aboutsummaryrefslogtreecommitdiff
path: root/lib/StaticAnalyzer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/StaticAnalyzer')
-rw-r--r--lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp24
-rw-r--r--lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h31
-rw-r--r--lib/StaticAnalyzer/Checkers/AllocationState.h5
-rw-r--r--lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp27
-rw-r--r--lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp102
-rw-r--r--lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/CMakeLists.txt14
-rw-r--r--lib/StaticAnalyzer/Checkers/CStringChecker.cpp150
-rw-r--r--lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp46
-rw-r--r--lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp103
-rw-r--r--lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp22
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp31
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/ChrootChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ClangCheckers.cpp32
-rw-r--r--lib/StaticAnalyzer/Checkers/ClangSACheckers.h37
-rw-r--r--lib/StaticAnalyzer/Checkers/CloneChecker.cpp16
-rw-r--r--lib/StaticAnalyzer/Checkers/ConversionChecker.cpp57
-rw-r--r--lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp9
-rw-r--r--lib/StaticAnalyzer/Checkers/DebugCheckers.cpp25
-rw-r--r--lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp15
-rw-r--r--lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp11
-rw-r--r--lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp11
-rw-r--r--lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp128
-rw-r--r--lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp115
-rw-r--r--lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/GTestChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp216
-rw-r--r--lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp158
-rw-r--r--lib/StaticAnalyzer/Checkers/InterCheckerAPI.h3
-rw-r--r--lib/StaticAnalyzer/Checkers/IteratorChecker.cpp1414
-rw-r--r--lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h1
-rw-r--r--lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp5
-rw-r--r--lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp53
-rw-r--r--lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocChecker.cpp161
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp11
-rw-r--r--lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp525
-rw-r--r--lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/MoveChecker.cpp740
-rw-r--r--lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp42
-rw-r--r--lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp11
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp19
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp9
-rw-r--r--lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/PaddingChecker.cpp45
-rw-r--r--lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp4156
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp1547
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h393
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp794
-rw-r--r--lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h85
-rw-r--r--lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp36
-rw-r--r--lib/StaticAnalyzer/Checkers/SelectorExtras.h46
-rw-r--r--lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp8
-rw-r--r--lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/StreamChecker.cpp36
-rw-r--r--lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/TraversalChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp195
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp11
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h349
-rw-r--r--lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp538
-rw-r--r--lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp282
-rw-r--r--lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp688
-rw-r--r--lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp6
-rw-r--r--lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp4
-rw-r--r--lib/StaticAnalyzer/Checkers/ValistChecker.cpp7
-rw-r--r--lib/StaticAnalyzer/Checkers/VforkChecker.cpp2
-rw-r--r--lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp9
-rw-r--r--lib/StaticAnalyzer/Core/AnalysisManager.cpp49
-rw-r--r--lib/StaticAnalyzer/Core/AnalyzerOptions.cpp483
-rw-r--r--lib/StaticAnalyzer/Core/BasicValueFactory.cpp2
-rw-r--r--lib/StaticAnalyzer/Core/BugReporter.cpp141
-rw-r--r--lib/StaticAnalyzer/Core/BugReporterVisitors.cpp750
-rw-r--r--lib/StaticAnalyzer/Core/CMakeLists.txt9
-rw-r--r--lib/StaticAnalyzer/Core/CallEvent.cpp162
-rw-r--r--lib/StaticAnalyzer/Core/Checker.cpp2
-rw-r--r--lib/StaticAnalyzer/Core/CheckerHelpers.cpp8
-rw-r--r--lib/StaticAnalyzer/Core/CheckerManager.cpp9
-rw-r--r--lib/StaticAnalyzer/Core/CheckerRegistry.cpp190
-rw-r--r--lib/StaticAnalyzer/Core/CommonBugCategories.cpp4
-rw-r--r--lib/StaticAnalyzer/Core/CoreEngine.cpp32
-rw-r--r--lib/StaticAnalyzer/Core/DynamicTypeMap.cpp5
-rw-r--r--lib/StaticAnalyzer/Core/Environment.cpp22
-rw-r--r--lib/StaticAnalyzer/Core/ExplodedGraph.cpp30
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngine.cpp545
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngineC.cpp18
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngineCXX.cpp125
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp93
-rw-r--r--lib/StaticAnalyzer/Core/ExprEngineObjC.cpp18
-rw-r--r--lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp63
-rw-r--r--lib/StaticAnalyzer/Core/LoopWidening.cpp11
-rw-r--r--lib/StaticAnalyzer/Core/MemRegion.cpp81
-rw-r--r--lib/StaticAnalyzer/Core/PathDiagnostic.cpp44
-rw-r--r--lib/StaticAnalyzer/Core/PlistDiagnostics.cpp892
-rw-r--r--lib/StaticAnalyzer/Core/ProgramState.cpp99
-rw-r--r--lib/StaticAnalyzer/Core/RangeConstraintManager.cpp2
-rw-r--r--lib/StaticAnalyzer/Core/RangedConstraintManager.cpp5
-rw-r--r--lib/StaticAnalyzer/Core/RegionStore.cpp175
-rw-r--r--lib/StaticAnalyzer/Core/RetainSummaryManager.cpp1229
-rw-r--r--lib/StaticAnalyzer/Core/SMTConstraintManager.cpp181
-rw-r--r--lib/StaticAnalyzer/Core/SValBuilder.cpp15
-rw-r--r--lib/StaticAnalyzer/Core/SVals.cpp6
-rw-r--r--lib/StaticAnalyzer/Core/SarifDiagnostics.cpp349
-rw-r--r--lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp26
-rw-r--r--lib/StaticAnalyzer/Core/Store.cpp45
-rw-r--r--lib/StaticAnalyzer/Core/SymbolManager.cpp17
-rw-r--r--lib/StaticAnalyzer/Core/TaintManager.cpp23
-rw-r--r--lib/StaticAnalyzer/Core/WorkList.cpp62
-rw-r--r--lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp304
-rw-r--r--lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp156
-rw-r--r--lib/StaticAnalyzer/Frontend/CMakeLists.txt1
-rw-r--r--lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp196
-rw-r--r--lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp247
-rw-r--r--lib/StaticAnalyzer/Frontend/ModelInjector.cpp2
-rw-r--r--lib/StaticAnalyzer/README.txt12
159 files changed, 11696 insertions, 9038 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp
deleted file mode 100644
index 3dec8a58c929..000000000000
--- a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-//=- AllocationDiagnostics.cpp - Config options for allocation diags *- C++ -*-//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// Declares the configuration functions for leaks/allocation diagnostics.
-//
-//===--------------------------
-
-#include "AllocationDiagnostics.h"
-
-namespace clang {
-namespace ento {
-
-bool shouldIncludeAllocationSiteInLeakDiagnostics(AnalyzerOptions &AOpts) {
- return AOpts.getBooleanOption("leak-diagnostics-reference-allocation",
- false);
-}
-
-}}
diff --git a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h
deleted file mode 100644
index 62b7fab0739a..000000000000
--- a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h
+++ /dev/null
@@ -1,31 +0,0 @@
-//=--- AllocationDiagnostics.h - Config options for allocation diags *- C++ -*-//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// Declares the configuration functions for leaks/allocation diagnostics.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONDIAGNOSTICS_H
-#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONDIAGNOSTICS_H
-
-#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
-
-namespace clang { namespace ento {
-
-/// Returns true if leak diagnostics should directly reference
-/// the allocatin site (where possible).
-///
-/// The default is false.
-///
-bool shouldIncludeAllocationSiteInLeakDiagnostics(AnalyzerOptions &AOpts);
-
-}}
-
-#endif
-
diff --git a/lib/StaticAnalyzer/Checkers/AllocationState.h b/lib/StaticAnalyzer/Checkers/AllocationState.h
index a6908bd7a651..c8193f77f928 100644
--- a/lib/StaticAnalyzer/Checkers/AllocationState.h
+++ b/lib/StaticAnalyzer/Checkers/AllocationState.h
@@ -26,6 +26,11 @@ ProgramStateRef markReleased(ProgramStateRef State, SymbolRef Sym,
/// AF_InnerBuffer symbols.
std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym);
+/// 'Sym' represents a pointer to the inner buffer of a container object.
+/// This function looks up the memory region of that object in
+/// DanglingInternalBufferChecker's program state map.
+const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym);
+
} // end namespace allocation_state
} // end namespace ento
diff --git a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp
index e4cdc500de6a..b5d0f6620a1d 100644
--- a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp
@@ -14,8 +14,9 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ExprCXX.h"
+#include "clang/Analysis/CFGStmtMap.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
@@ -37,14 +38,15 @@ class AnalysisOrderChecker
check::PostStmt<OffsetOfExpr>,
check::PreCall,
check::PostCall,
+ check::EndFunction,
check::NewAllocator,
check::Bind,
check::RegionChanges,
check::LiveSymbols> {
bool isCallbackEnabled(AnalyzerOptions &Opts, StringRef CallbackName) const {
- return Opts.getBooleanOption("*", false, this) ||
- Opts.getBooleanOption(CallbackName, false, this);
+ return Opts.getCheckerBooleanOption("*", false, this) ||
+ Opts.getCheckerBooleanOption(CallbackName, false, this);
}
bool isCallbackEnabled(CheckerContext &C, StringRef CallbackName) const {
@@ -54,7 +56,7 @@ class AnalysisOrderChecker
bool isCallbackEnabled(ProgramStateRef State, StringRef CallbackName) const {
AnalyzerOptions &Opts = State->getStateManager().getOwningEngine()
- ->getAnalysisManager().getAnalyzerOptions();
+ .getAnalysisManager().getAnalyzerOptions();
return isCallbackEnabled(Opts, CallbackName);
}
@@ -121,6 +123,23 @@ public:
}
}
+ void checkEndFunction(const ReturnStmt *S, CheckerContext &C) const {
+ if (isCallbackEnabled(C, "EndFunction")) {
+ llvm::errs() << "EndFunction\nReturnStmt: " << (S ? "yes" : "no") << "\n";
+ if (!S)
+ return;
+
+ llvm::errs() << "CFGElement: ";
+ CFGStmtMap *Map = C.getCurrentAnalysisDeclContext()->getCFGStmtMap();
+ CFGElement LastElement = Map->getBlock(S)->back();
+
+ if (LastElement.getAs<CFGStmt>())
+ llvm::errs() << "CFGStmt\n";
+ else if (LastElement.getAs<CFGAutomaticObjDtor>())
+ llvm::errs() << "CFGAutomaticObjDtor\n";
+ }
+ }
+
void checkNewAllocator(const CXXNewExpr *CNE, SVal Target,
CheckerContext &C) const {
if (isCallbackEnabled(C, "NewAllocator"))
diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
index aadc6bac8d00..5e01012401b2 100644
--- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp
@@ -8,7 +8,7 @@
//===----------------------------------------------------------------------===//
// This file reports various statistics about analyzer visitation.
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclObjC.h"
#include "clang/Basic/SourceManager.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
index c092610afe2b..20f3092fdba4 100644
--- a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
index 933380d494a4..26887be9f258 100644
--- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
+++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/CharUnits.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
index 7d6358acbbac..577b5349f62e 100644
--- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
+++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp
@@ -13,14 +13,14 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
-#include "SelectorExtras.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/StmtObjC.h"
#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
+#include "clang/Analysis/SelectorExtras.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -36,6 +36,7 @@
using namespace clang;
using namespace ento;
+using namespace llvm;
namespace {
class APIMisuse : public BugType {
@@ -156,6 +157,11 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C,
if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue())
return;
+ // NOTE: We cannot throw non-fatal errors from warnIfNilExpr,
+ // because it's called multiple times from some callers, so it'd cause
+ // an unwanted state split if two or more non-fatal errors are thrown
+ // within the same checker callback. For now we don't want to, but
+ // it'll need to be fixed if we ever want to.
if (ExplodedNode *N = C.generateErrorNode()) {
SmallString<128> sbuf;
llvm::raw_svector_ostream os(sbuf);
@@ -208,7 +214,7 @@ void NilArgChecker::generateBugReport(ExplodedNode *N,
auto R = llvm::make_unique<BugReport>(*BT, Msg, N);
R->addRange(Range);
- bugreporter::trackNullOrUndefValue(N, E, *R);
+ bugreporter::trackExpressionValue(N, E, *R);
C.emitReport(std::move(R));
}
@@ -526,93 +532,59 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE,
//===----------------------------------------------------------------------===//
namespace {
-class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > {
- mutable std::unique_ptr<APIMisuse> BT;
- mutable IdentifierInfo *Retain, *Release, *MakeCollectable, *Autorelease;
+class CFRetainReleaseChecker : public Checker<check::PreCall> {
+ mutable APIMisuse BT{this, "null passed to CF memory management function"};
+ CallDescription CFRetain{"CFRetain", 1},
+ CFRelease{"CFRelease", 1},
+ CFMakeCollectable{"CFMakeCollectable", 1},
+ CFAutorelease{"CFAutorelease", 1};
public:
- CFRetainReleaseChecker()
- : Retain(nullptr), Release(nullptr), MakeCollectable(nullptr),
- Autorelease(nullptr) {}
- void checkPreStmt(const CallExpr *CE, CheckerContext &C) const;
+ void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
};
} // end anonymous namespace
-void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE,
+void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
- // If the CallExpr doesn't have exactly 1 argument just give up checking.
- if (CE->getNumArgs() != 1)
+ // TODO: Make this check part of CallDescription.
+ if (!Call.isGlobalCFunction())
return;
- ProgramStateRef state = C.getState();
- const FunctionDecl *FD = C.getCalleeDecl(CE);
- if (!FD)
- return;
-
- if (!BT) {
- ASTContext &Ctx = C.getASTContext();
- Retain = &Ctx.Idents.get("CFRetain");
- Release = &Ctx.Idents.get("CFRelease");
- MakeCollectable = &Ctx.Idents.get("CFMakeCollectable");
- Autorelease = &Ctx.Idents.get("CFAutorelease");
- BT.reset(new APIMisuse(
- this, "null passed to CF memory management function"));
- }
-
// Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease.
- const IdentifierInfo *FuncII = FD->getIdentifier();
- if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable ||
- FuncII == Autorelease))
+ if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) ||
+ Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease)))
return;
- // FIXME: The rest of this just checks that the argument is non-null.
- // It should probably be refactored and combined with NonNullParamChecker.
-
// Get the argument's value.
- const Expr *Arg = CE->getArg(0);
- SVal ArgVal = C.getSVal(Arg);
+ SVal ArgVal = Call.getArgSVal(0);
Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>();
if (!DefArgVal)
return;
- // Get a NULL value.
- SValBuilder &svalBuilder = C.getSValBuilder();
- DefinedSVal zero =
- svalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>();
-
- // Make an expression asserting that they're equal.
- DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal);
-
- // Are they equal?
- ProgramStateRef stateTrue, stateFalse;
- std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull);
+ // Is it null?
+ ProgramStateRef state = C.getState();
+ ProgramStateRef stateNonNull, stateNull;
+ std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal);
- if (stateTrue && !stateFalse) {
- ExplodedNode *N = C.generateErrorNode(stateTrue);
+ if (!stateNonNull) {
+ ExplodedNode *N = C.generateErrorNode(stateNull);
if (!N)
return;
- const char *description;
- if (FuncII == Retain)
- description = "Null pointer argument in call to CFRetain";
- else if (FuncII == Release)
- description = "Null pointer argument in call to CFRelease";
- else if (FuncII == MakeCollectable)
- description = "Null pointer argument in call to CFMakeCollectable";
- else if (FuncII == Autorelease)
- description = "Null pointer argument in call to CFAutorelease";
- else
- llvm_unreachable("impossible case");
+ SmallString<64> Str;
+ raw_svector_ostream OS(Str);
+ OS << "Null pointer argument in call to "
+ << cast<FunctionDecl>(Call.getDecl())->getName();
- auto report = llvm::make_unique<BugReport>(*BT, description, N);
- report->addRange(Arg->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, Arg, *report);
+ auto report = llvm::make_unique<BugReport>(BT, OS.str(), N);
+ report->addRange(Call.getArgSourceRange(0));
+ bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report);
C.emitReport(std::move(report));
return;
}
// From here on, we know the argument is non-null.
- C.addTransition(stateFalse);
+ C.addTransition(stateNonNull);
}
//===----------------------------------------------------------------------===//
@@ -828,7 +800,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
//===----------------------------------------------------------------------===//
// The map from container symbol to the container count symbol.
-// We currently will remember the last countainer count symbol encountered.
+// We currently will remember the last container count symbol encountered.
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef)
REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool)
diff --git a/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
index c31f2794df6a..00d08b371f37 100644
--- a/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
index f26f73129e78..3008eddd397e 100644
--- a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
index 0e781d08e24c..f98027942e18 100644
--- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Basic/Builtins.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -101,9 +101,10 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE,
// This must be resolvable at compile time, so we defer to the constant
// evaluator for a value.
SVal V = UnknownVal();
- llvm::APSInt Result;
- if (CE->EvaluateAsInt(Result, C.getASTContext(), Expr::SE_NoSideEffects)) {
+ Expr::EvalResult EVResult;
+ if (CE->EvaluateAsInt(EVResult, C.getASTContext(), Expr::SE_NoSideEffects)) {
// Make sure the result has the correct type.
+ llvm::APSInt Result = EVResult.Val.getInt();
SValBuilder &SVB = C.getSValBuilder();
BasicValueFactory &BVF = SVB.getBasicValueFactory();
BVF.getAPSIntType(CE->getType()).apply(Result);
diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 5bb4770b5675..10fb0bd3536c 100644
--- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -3,7 +3,6 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangStaticAnalyzerCheckers
- AllocationDiagnostics.cpp
AnalysisOrderChecker.cpp
AnalyzerStatsChecker.cpp
ArrayBoundChecker.cpp
@@ -23,7 +22,6 @@ add_clang_library(clangStaticAnalyzerCheckers
CheckSizeofPointer.cpp
CheckerDocumentation.cpp
ChrootChecker.cpp
- ClangCheckers.cpp
CloneChecker.cpp
ConversionChecker.cpp
CXXSelfAssignmentChecker.cpp
@@ -35,6 +33,7 @@ add_clang_library(clangStaticAnalyzerCheckers
DivZeroChecker.cpp
DynamicTypePropagation.cpp
DynamicTypeChecker.cpp
+ EnumCastOutOfRangeChecker.cpp
ExprInspectionChecker.cpp
FixedAddressChecker.cpp
GCDAntipatternChecker.cpp
@@ -52,7 +51,7 @@ add_clang_library(clangStaticAnalyzerCheckers
MallocOverflowSecurityChecker.cpp
MallocSizeofChecker.cpp
MmapWriteExecChecker.cpp
- MisusedMovedObjectChecker.cpp
+ MoveChecker.cpp
MPI-Checker/MPIBugReporter.cpp
MPI-Checker/MPIChecker.cpp
MPI-Checker/MPIFunctionClassifier.cpp
@@ -76,7 +75,8 @@ add_clang_library(clangStaticAnalyzerCheckers
PointerArithChecker.cpp
PointerSubChecker.cpp
PthreadLockChecker.cpp
- RetainCountChecker.cpp
+ RetainCountChecker/RetainCountChecker.cpp
+ RetainCountChecker/RetainCountDiagnostics.cpp
ReturnPointerRangeChecker.cpp
ReturnUndefChecker.cpp
RunLoopAutoreleaseLeakChecker.cpp
@@ -93,7 +93,8 @@ add_clang_library(clangStaticAnalyzerCheckers
UndefResultChecker.cpp
UndefinedArraySubscriptChecker.cpp
UndefinedAssignmentChecker.cpp
- UninitializedObjectChecker.cpp
+ UninitializedObject/UninitializedObjectChecker.cpp
+ UninitializedObject/UninitializedPointee.cpp
UnixAPIChecker.cpp
UnreachableCodeChecker.cpp
VforkChecker.cpp
@@ -101,9 +102,6 @@ add_clang_library(clangStaticAnalyzerCheckers
ValistChecker.cpp
VirtualCallChecker.cpp
- DEPENDS
- ClangSACheckers
-
LINK_LIBS
clangAST
clangASTMatchers
diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
index 12a576e5d80d..8bffada69b9b 100644
--- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "InterCheckerAPI.h"
#include "clang/Basic/CharInfo.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -124,6 +124,7 @@ public:
void evalStdCopyBackward(CheckerContext &C, const CallExpr *CE) const;
void evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const;
void evalMemset(CheckerContext &C, const CallExpr *CE) const;
+ void evalBzero(CheckerContext &C, const CallExpr *CE) const;
// Utility methods
std::pair<ProgramStateRef , ProgramStateRef >
@@ -158,7 +159,7 @@ public:
static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
const MemRegion *MR);
- static bool memsetAux(const Expr *DstBuffer, const Expr *CharE,
+ static bool memsetAux(const Expr *DstBuffer, SVal CharE,
const Expr *Size, CheckerContext &C,
ProgramStateRef &State);
@@ -187,7 +188,7 @@ public:
const Expr *Buf,
const char *message = nullptr,
bool WarnAboutSize = false) const {
- // This is a convenience override.
+ // This is a convenience overload.
return CheckBufferAccess(C, state, Size, Buf, nullptr, message, nullptr,
WarnAboutSize);
}
@@ -553,7 +554,8 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State,
BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Null.get());
auto Report = llvm::make_unique<BugReport>(*BT, WarningMsg, N);
Report->addRange(S->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, S, *Report);
+ if (const auto *Ex = dyn_cast<Expr>(S))
+ bugreporter::trackExpressionValue(N, Ex, *Report);
C.emitReport(std::move(Report));
}
}
@@ -1004,11 +1006,10 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx,
}
}
-bool CStringChecker::memsetAux(const Expr *DstBuffer, const Expr *CharE,
+bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal,
const Expr *Size, CheckerContext &C,
ProgramStateRef &State) {
SVal MemVal = C.getSVal(DstBuffer);
- SVal CharVal = C.getSVal(CharE);
SVal SizeVal = C.getSVal(Size);
const MemRegion *MR = MemVal.getAsRegion();
if (!MR)
@@ -2183,13 +2184,59 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const {
// According to the values of the arguments, bind the value of the second
// argument to the destination buffer and set string length, or just
// invalidate the destination buffer.
- if (!memsetAux(Mem, CharE, Size, C, State))
+ if (!memsetAux(Mem, C.getSVal(CharE), Size, C, State))
return;
State = State->BindExpr(CE, LCtx, MemVal);
C.addTransition(State);
}
+void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const {
+ if (CE->getNumArgs() != 2)
+ return;
+
+ CurrentFunctionDescription = "memory clearance function";
+
+ const Expr *Mem = CE->getArg(0);
+ const Expr *Size = CE->getArg(1);
+ SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy);
+
+ ProgramStateRef State = C.getState();
+
+ // See if the size argument is zero.
+ SVal SizeVal = C.getSVal(Size);
+ QualType SizeTy = Size->getType();
+
+ ProgramStateRef StateZeroSize, StateNonZeroSize;
+ std::tie(StateZeroSize, StateNonZeroSize) =
+ assumeZero(C, State, SizeVal, SizeTy);
+
+ // If the size is zero, there won't be any actual memory access,
+ // In this case we just return.
+ if (StateZeroSize && !StateNonZeroSize) {
+ C.addTransition(StateZeroSize);
+ return;
+ }
+
+ // Get the value of the memory area.
+ SVal MemVal = C.getSVal(Mem);
+
+ // Ensure the memory area is not null.
+ // If it is NULL there will be a NULL pointer dereference.
+ State = checkNonNull(C, StateNonZeroSize, Mem, MemVal);
+ if (!State)
+ return;
+
+ State = CheckBufferAccess(C, State, Size, Mem);
+ if (!State)
+ return;
+
+ if (!memsetAux(Mem, Zero, Size, C, State))
+ return;
+
+ C.addTransition(State);
+}
+
static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) {
IdentifierInfo *II = FD->getIdentifier();
if (!II)
@@ -2207,60 +2254,86 @@ static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) {
// The driver method, and other Checker callbacks.
//===----------------------------------------------------------------------===//
-bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
+static CStringChecker::FnCheck identifyCall(const CallExpr *CE,
+ CheckerContext &C) {
const FunctionDecl *FDecl = C.getCalleeDecl(CE);
-
if (!FDecl)
- return false;
+ return nullptr;
+
+ // Pro-actively check that argument types are safe to do arithmetic upon.
+ // We do not want to crash if someone accidentally passes a structure
+ // into, say, a C++ overload of any of these functions.
+ if (isCPPStdLibraryFunction(FDecl, "copy")) {
+ if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType())
+ return nullptr;
+ return &CStringChecker::evalStdCopy;
+ } else if (isCPPStdLibraryFunction(FDecl, "copy_backward")) {
+ if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType())
+ return nullptr;
+ return &CStringChecker::evalStdCopyBackward;
+ } else {
+ // An umbrella check for all C library functions.
+ for (auto I: CE->arguments()) {
+ QualType T = I->getType();
+ if (!T->isIntegralOrEnumerationType() && !T->isPointerType())
+ return nullptr;
+ }
+ }
// FIXME: Poorly-factored string switches are slow.
- FnCheck evalFunction = nullptr;
if (C.isCLibraryFunction(FDecl, "memcpy"))
- evalFunction = &CStringChecker::evalMemcpy;
+ return &CStringChecker::evalMemcpy;
else if (C.isCLibraryFunction(FDecl, "mempcpy"))
- evalFunction = &CStringChecker::evalMempcpy;
+ return &CStringChecker::evalMempcpy;
else if (C.isCLibraryFunction(FDecl, "memcmp"))
- evalFunction = &CStringChecker::evalMemcmp;
+ return &CStringChecker::evalMemcmp;
else if (C.isCLibraryFunction(FDecl, "memmove"))
- evalFunction = &CStringChecker::evalMemmove;
- else if (C.isCLibraryFunction(FDecl, "memset"))
- evalFunction = &CStringChecker::evalMemset;
+ return &CStringChecker::evalMemmove;
+ else if (C.isCLibraryFunction(FDecl, "memset") ||
+ C.isCLibraryFunction(FDecl, "explicit_memset"))
+ return &CStringChecker::evalMemset;
else if (C.isCLibraryFunction(FDecl, "strcpy"))
- evalFunction = &CStringChecker::evalStrcpy;
+ return &CStringChecker::evalStrcpy;
else if (C.isCLibraryFunction(FDecl, "strncpy"))
- evalFunction = &CStringChecker::evalStrncpy;
+ return &CStringChecker::evalStrncpy;
else if (C.isCLibraryFunction(FDecl, "stpcpy"))
- evalFunction = &CStringChecker::evalStpcpy;
+ return &CStringChecker::evalStpcpy;
else if (C.isCLibraryFunction(FDecl, "strlcpy"))
- evalFunction = &CStringChecker::evalStrlcpy;
+ return &CStringChecker::evalStrlcpy;
else if (C.isCLibraryFunction(FDecl, "strcat"))
- evalFunction = &CStringChecker::evalStrcat;
+ return &CStringChecker::evalStrcat;
else if (C.isCLibraryFunction(FDecl, "strncat"))
- evalFunction = &CStringChecker::evalStrncat;
+ return &CStringChecker::evalStrncat;
else if (C.isCLibraryFunction(FDecl, "strlcat"))
- evalFunction = &CStringChecker::evalStrlcat;
+ return &CStringChecker::evalStrlcat;
else if (C.isCLibraryFunction(FDecl, "strlen"))
- evalFunction = &CStringChecker::evalstrLength;
+ return &CStringChecker::evalstrLength;
else if (C.isCLibraryFunction(FDecl, "strnlen"))
- evalFunction = &CStringChecker::evalstrnLength;
+ return &CStringChecker::evalstrnLength;
else if (C.isCLibraryFunction(FDecl, "strcmp"))
- evalFunction = &CStringChecker::evalStrcmp;
+ return &CStringChecker::evalStrcmp;
else if (C.isCLibraryFunction(FDecl, "strncmp"))
- evalFunction = &CStringChecker::evalStrncmp;
+ return &CStringChecker::evalStrncmp;
else if (C.isCLibraryFunction(FDecl, "strcasecmp"))
- evalFunction = &CStringChecker::evalStrcasecmp;
+ return &CStringChecker::evalStrcasecmp;
else if (C.isCLibraryFunction(FDecl, "strncasecmp"))
- evalFunction = &CStringChecker::evalStrncasecmp;
+ return &CStringChecker::evalStrncasecmp;
else if (C.isCLibraryFunction(FDecl, "strsep"))
- evalFunction = &CStringChecker::evalStrsep;
+ return &CStringChecker::evalStrsep;
else if (C.isCLibraryFunction(FDecl, "bcopy"))
- evalFunction = &CStringChecker::evalBcopy;
+ return &CStringChecker::evalBcopy;
else if (C.isCLibraryFunction(FDecl, "bcmp"))
- evalFunction = &CStringChecker::evalMemcmp;
- else if (isCPPStdLibraryFunction(FDecl, "copy"))
- evalFunction = &CStringChecker::evalStdCopy;
- else if (isCPPStdLibraryFunction(FDecl, "copy_backward"))
- evalFunction = &CStringChecker::evalStdCopyBackward;
+ return &CStringChecker::evalMemcmp;
+ else if (C.isCLibraryFunction(FDecl, "bzero") ||
+ C.isCLibraryFunction(FDecl, "explicit_bzero"))
+ return &CStringChecker::evalBzero;
+
+ return nullptr;
+}
+
+bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
+
+ FnCheck evalFunction = identifyCall(CE, C);
// If the callee isn't a string function, let another checker handle it.
if (!evalFunction)
@@ -2384,9 +2457,6 @@ void CStringChecker::checkLiveSymbols(ProgramStateRef state,
void CStringChecker::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const {
- if (!SR.hasDeadSymbols())
- return;
-
ProgramStateRef state = C.getState();
CStringLengthTy Entries = state->get<CStringLength>();
if (Entries.isEmpty())
diff --git a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp
index 8b4aa857e775..bbeb41c5f3cf 100644
--- a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp
@@ -12,7 +12,7 @@
// of bytes to copy.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Expr.h"
#include "clang/AST/OperationKinds.h"
#include "clang/AST/StmtVisitor.h"
@@ -90,7 +90,16 @@ class WalkAST: public StmtVisitor<WalkAST> {
/// strlcpy(dst, "abcd", 4);
/// strlcpy(dst + 3, "abcd", 2);
/// strlcpy(dst, "abcd", cpy);
- bool containsBadStrlcpyPattern(const CallExpr *CE);
+ /// Identify erroneous patterns in the last argument to strlcat - the number
+ /// of bytes to copy.
+ /// The bad pattern checked is when the last argument is basically
+ /// pointing to the destination buffer size or argument larger or
+ /// equal to.
+ /// char dst[2];
+ /// strlcat(dst, src2, sizeof(dst));
+ /// strlcat(dst, src2, 2);
+ /// strlcat(dst, src2, 10);
+ bool containsBadStrlcpyStrlcatPattern(const CallExpr *CE);
public:
WalkAST(const CheckerBase *Checker, BugReporter &BR, AnalysisDeclContext *AC)
@@ -142,15 +151,19 @@ bool WalkAST::containsBadStrncatPattern(const CallExpr *CE) {
return false;
}
-bool WalkAST::containsBadStrlcpyPattern(const CallExpr *CE) {
+bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) {
if (CE->getNumArgs() != 3)
return false;
+ const FunctionDecl *FD = CE->getDirectCallee();
+ bool Append = CheckerContext::isCLibraryFunction(FD, "strlcat");
const Expr *DstArg = CE->getArg(0);
const Expr *LenArg = CE->getArg(2);
const auto *DstArgDecl = dyn_cast<DeclRefExpr>(DstArg->IgnoreParenImpCasts());
const auto *LenArgDecl = dyn_cast<DeclRefExpr>(LenArg->IgnoreParenLValueCasts());
uint64_t DstOff = 0;
+ if (isSizeof(LenArg, DstArg))
+ return false;
// - size_t dstlen = sizeof(dst)
if (LenArgDecl) {
const auto *LenArgVal = dyn_cast<VarDecl>(LenArgDecl->getDecl());
@@ -181,8 +194,14 @@ bool WalkAST::containsBadStrlcpyPattern(const CallExpr *CE) {
if (const auto *Buffer = dyn_cast<ConstantArrayType>(DstArgDecl->getType())) {
ASTContext &C = BR.getContext();
uint64_t BufferLen = C.getTypeSize(Buffer) / 8;
- if ((BufferLen - DstOff) < ILRawVal)
- return true;
+ auto RemainingBufferLen = BufferLen - DstOff;
+ if (Append) {
+ if (RemainingBufferLen <= ILRawVal)
+ return true;
+ } else {
+ if (RemainingBufferLen < ILRawVal)
+ return true;
+ }
}
}
}
@@ -219,8 +238,9 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
"C String API", os.str(), Loc,
LenArg->getSourceRange());
}
- } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy")) {
- if (containsBadStrlcpyPattern(CE)) {
+ } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy") ||
+ CheckerContext::isCLibraryFunction(FD, "strlcat")) {
+ if (containsBadStrlcpyStrlcatPattern(CE)) {
const Expr *DstArg = CE->getArg(0);
const Expr *LenArg = CE->getArg(2);
PathDiagnosticLocation Loc =
@@ -230,13 +250,17 @@ void WalkAST::VisitCallExpr(CallExpr *CE) {
SmallString<256> S;
llvm::raw_svector_ostream os(S);
- os << "The third argument is larger than the size of the input buffer. ";
+ os << "The third argument allows to potentially copy more bytes than it should. ";
+ os << "Replace with the value ";
if (!DstName.empty())
- os << "Replace with the value 'sizeof(" << DstName << ")` or lower";
+ os << "sizeof(" << DstName << ")";
+ else
+ os << "sizeof(<destination buffer>)";
+ os << " or lower";
BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument",
- "C String API", os.str(), Loc,
- LenArg->getSourceRange());
+ "C String API", os.str(), Loc,
+ LenArg->getSourceRange());
}
}
diff --git a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp
index d1d37c75dfcc..0b539e1188eb 100644
--- a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp
@@ -18,7 +18,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
diff --git a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
index 20a46843e23e..ef30dc74c39d 100644
--- a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -108,7 +108,7 @@ void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C,
R->addRange(BadE->getSourceRange());
if (BadE->isGLValue())
BadE = bugreporter::getDerefExpr(BadE);
- bugreporter::trackNullOrUndefValue(N, BadE, *R);
+ bugreporter::trackExpressionValue(N, BadE, *R);
}
C.emitReport(std::move(R));
}
@@ -185,9 +185,9 @@ bool CallAndMessageChecker::uninitRefOrPointer(
LazyInit_BT(BD, BT);
auto R = llvm::make_unique<BugReport>(*BT, Os.str(), N);
R->addRange(ArgRange);
- if (ArgEx) {
- bugreporter::trackNullOrUndefValue(N, ArgEx, *R);
- }
+ if (ArgEx)
+ bugreporter::trackExpressionValue(N, ArgEx, *R);
+
C.emitReport(std::move(R));
}
return true;
@@ -196,6 +196,47 @@ bool CallAndMessageChecker::uninitRefOrPointer(
return false;
}
+namespace {
+class FindUninitializedField {
+public:
+ SmallVector<const FieldDecl *, 10> FieldChain;
+
+private:
+ StoreManager &StoreMgr;
+ MemRegionManager &MrMgr;
+ Store store;
+
+public:
+ FindUninitializedField(StoreManager &storeMgr, MemRegionManager &mrMgr,
+ Store s)
+ : StoreMgr(storeMgr), MrMgr(mrMgr), store(s) {}
+
+ bool Find(const TypedValueRegion *R) {
+ QualType T = R->getValueType();
+ if (const RecordType *RT = T->getAsStructureType()) {
+ const RecordDecl *RD = RT->getDecl()->getDefinition();
+ assert(RD && "Referred record has no definition");
+ for (const auto *I : RD->fields()) {
+ const FieldRegion *FR = MrMgr.getFieldRegion(I, R);
+ FieldChain.push_back(I);
+ T = I->getType();
+ if (T->getAsStructureType()) {
+ if (Find(FR))
+ return true;
+ } else {
+ const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR));
+ if (V.isUndef())
+ return true;
+ }
+ FieldChain.pop_back();
+ }
+ }
+
+ return false;
+ }
+};
+} // namespace
+
bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
SVal V,
SourceRange ArgRange,
@@ -223,7 +264,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
R->addRange(ArgRange);
if (ArgEx)
- bugreporter::trackNullOrUndefValue(N, ArgEx, *R);
+ bugreporter::trackExpressionValue(N, ArgEx, *R);
C.emitReport(std::move(R));
}
return true;
@@ -232,47 +273,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
if (!CheckUninitFields)
return false;
- if (Optional<nonloc::LazyCompoundVal> LV =
- V.getAs<nonloc::LazyCompoundVal>()) {
-
- class FindUninitializedField {
- public:
- SmallVector<const FieldDecl *, 10> FieldChain;
- private:
- StoreManager &StoreMgr;
- MemRegionManager &MrMgr;
- Store store;
- public:
- FindUninitializedField(StoreManager &storeMgr,
- MemRegionManager &mrMgr, Store s)
- : StoreMgr(storeMgr), MrMgr(mrMgr), store(s) {}
-
- bool Find(const TypedValueRegion *R) {
- QualType T = R->getValueType();
- if (const RecordType *RT = T->getAsStructureType()) {
- const RecordDecl *RD = RT->getDecl()->getDefinition();
- assert(RD && "Referred record has no definition");
- for (const auto *I : RD->fields()) {
- const FieldRegion *FR = MrMgr.getFieldRegion(I, R);
- FieldChain.push_back(I);
- T = I->getType();
- if (T->getAsStructureType()) {
- if (Find(FR))
- return true;
- }
- else {
- const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR));
- if (V.isUndef())
- return true;
- }
- FieldChain.pop_back();
- }
- }
-
- return false;
- }
- };
-
+ if (auto LV = V.getAs<nonloc::LazyCompoundVal>()) {
const LazyCompoundValData *D = LV->getCVData();
FindUninitializedField F(C.getState()->getStateManager().getStoreManager(),
C.getSValBuilder().getRegionManager(),
@@ -305,6 +306,8 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C,
auto R = llvm::make_unique<BugReport>(*BT, os.str(), N);
R->addRange(ArgRange);
+ if (ArgEx)
+ bugreporter::trackExpressionValue(N, ArgEx, *R);
// FIXME: enhance track back for uninitialized value for arbitrary
// memregions
C.emitReport(std::move(R));
@@ -364,7 +367,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE,
Desc = "Argument to 'delete' is uninitialized";
BugType *BT = BT_cxx_delete_undef.get();
auto R = llvm::make_unique<BugReport>(*BT, Desc, N);
- bugreporter::trackNullOrUndefValue(N, DE, *R);
+ bugreporter::trackExpressionValue(N, DE, *R);
C.emitReport(std::move(R));
return;
}
@@ -493,7 +496,7 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg,
// FIXME: getTrackNullOrUndefValueVisitor can't handle "super" yet.
if (const Expr *ReceiverE = ME->getInstanceReceiver())
- bugreporter::trackNullOrUndefValue(N, ReceiverE, *R);
+ bugreporter::trackExpressionValue(N, ReceiverE, *R);
C.emitReport(std::move(R));
}
return;
@@ -534,7 +537,7 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C,
report->addRange(ME->getReceiverRange());
// FIXME: This won't track "self" in messages to super.
if (const Expr *receiver = ME->getInstanceReceiver()) {
- bugreporter::trackNullOrUndefValue(N, receiver, *report);
+ bugreporter::trackExpressionValue(N, receiver, *report);
}
C.emitReport(std::move(report));
}
diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
index 059553b21995..5deb62d32311 100644
--- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp
@@ -11,7 +11,7 @@
// whether the size of the symbolic region is a multiple of the size of T.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/CharUnits.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
index 00e903355720..2bd3879627cb 100644
--- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
index f4d2e32cef11..00a912f27a8d 100644
--- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp
@@ -28,7 +28,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
@@ -178,20 +178,12 @@ private:
};
} // End anonymous namespace.
-typedef llvm::ImmutableSet<SymbolRef> SymbolSet;
/// Maps from the symbol for a class instance to the set of
/// symbols remaining that must be released in -dealloc.
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(SymbolSet, SymbolRef)
REGISTER_MAP_WITH_PROGRAMSTATE(UnreleasedIvarMap, SymbolRef, SymbolSet)
-namespace clang {
-namespace ento {
-template<> struct ProgramStateTrait<SymbolSet>
-: public ProgramStatePartialTrait<SymbolSet> {
- static void *GDMIndex() { static int index = 0; return &index; }
-};
-}
-}
/// An AST check that diagnose when the class requires a -dealloc method and
/// is missing one.
@@ -723,6 +715,10 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue,
bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue,
const ObjCMethodCall &M,
CheckerContext &C) const {
+ // TODO: Apart from unknown/undefined receivers, this may happen when
+ // dealloc is called as a class method. Should we warn?
+ if (!DeallocedValue)
+ return false;
// Find the property backing the instance variable that M
// is dealloc'ing.
@@ -761,15 +757,15 @@ ObjCDeallocChecker::ObjCDeallocChecker()
MissingReleaseBugType.reset(
new BugType(this, "Missing ivar release (leak)",
- categories::MemoryCoreFoundationObjectiveC));
+ categories::MemoryRefCount));
ExtraReleaseBugType.reset(
new BugType(this, "Extra ivar release",
- categories::MemoryCoreFoundationObjectiveC));
+ categories::MemoryRefCount));
MistakenDeallocBugType.reset(
new BugType(this, "Mistaken dealloc",
- categories::MemoryCoreFoundationObjectiveC));
+ categories::MemoryRefCount));
}
void ObjCDeallocChecker::initIdentifierInfoAndSelectors(
diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp
index cc4c0c3db846..fe6715595e6f 100644
--- a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Type.h"
diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
index 202233acffab..163ca9d8556f 100644
--- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/TargetInfo.h"
@@ -29,10 +29,10 @@ static bool isArc4RandomAvailable(const ASTContext &Ctx) {
const llvm::Triple &T = Ctx.getTargetInfo().getTriple();
return T.getVendor() == llvm::Triple::Apple ||
T.getOS() == llvm::Triple::CloudABI ||
- T.getOS() == llvm::Triple::FreeBSD ||
- T.getOS() == llvm::Triple::NetBSD ||
- T.getOS() == llvm::Triple::OpenBSD ||
- T.getOS() == llvm::Triple::DragonFly;
+ T.isOSFreeBSD() ||
+ T.isOSNetBSD() ||
+ T.isOSOpenBSD() ||
+ T.isOSDragonFly();
}
namespace {
@@ -188,7 +188,7 @@ void WalkAST::VisitForStmt(ForStmt *FS) {
}
//===----------------------------------------------------------------------===//
-// Check: floating poing variable used as loop counter.
+// Check: floating point variable used as loop counter.
// Originally: <rdar://problem/6336718>
// Implements: CERT security coding advisory FLP-30.
//===----------------------------------------------------------------------===//
@@ -597,9 +597,10 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) {
unsigned suffix = 0;
if (ArgSuffix.second >= 0) {
const Expr *suffixEx = CE->getArg((unsigned)ArgSuffix.second);
- llvm::APSInt Result;
- if (!suffixEx->EvaluateAsInt(Result, BR.getContext()))
+ Expr::EvalResult EVResult;
+ if (!suffixEx->EvaluateAsInt(EVResult, BR.getContext()))
return;
+ llvm::APSInt Result = EVResult.Val.getInt();
// FIXME: Issue a warning.
if (Result.isNegative())
return;
@@ -650,14 +651,14 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) {
const auto *Target = CE->getArg(0)->IgnoreImpCasts(),
*Source = CE->getArg(1)->IgnoreImpCasts();
- if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Target))
- if (const auto *Array = dyn_cast<ConstantArrayType>(DeclRef->getType())) {
- uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8;
- if (const auto *String = dyn_cast<StringLiteral>(Source)) {
- if (ArraySize >= String->getLength() + 1)
- return;
- }
+
+ if (const auto *Array = dyn_cast<ConstantArrayType>(Target->getType())) {
+ uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8;
+ if (const auto *String = dyn_cast<StringLiteral>(Source)) {
+ if (ArraySize >= String->getLength() + 1)
+ return;
}
+ }
// Issue a warning.
PathDiagnosticLocation CELoc =
diff --git a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
index e079a8cb12be..7688b713b06b 100644
--- a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
index 7862a4c25681..44fac0278bdd 100644
--- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
+++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -169,7 +169,7 @@ public:
/// This callback should be used by the checkers to aggressively clean
/// up/reduce the checker state, which is important for reducing the overall
/// memory usage. Specifically, if a checker keeps symbol specific information
- /// in the sate, it can and should be dropped after the symbol becomes dead.
+ /// in the state, it can and should be dropped after the symbol becomes dead.
/// In addition, reporting a bug as soon as the checker becomes dead leads to
/// more precise diagnostics. (For example, one should report that a malloced
/// variable is not freed right after it goes out of scope.)
diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
index b38992b0e030..673608db1a1d 100644
--- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp b/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp
deleted file mode 100644
index fb9e366c3de0..000000000000
--- a/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp
+++ /dev/null
@@ -1,32 +0,0 @@
-//===--- ClangCheckers.h - Provides builtin checkers ------------*- C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/StaticAnalyzer/Checkers/ClangCheckers.h"
-#include "clang/StaticAnalyzer/Core/CheckerRegistry.h"
-
-// FIXME: This is only necessary as long as there are checker registration
-// functions that do additional work besides mgr.registerChecker<CLASS>().
-// The only checkers that currently do this are:
-// - NSAutoreleasePoolChecker
-// - NSErrorChecker
-// - ObjCAtSyncChecker
-// It's probably worth including this information in Checkers.td to minimize
-// boilerplate code.
-#include "ClangSACheckers.h"
-
-using namespace clang;
-using namespace ento;
-
-void ento::registerBuiltinCheckers(CheckerRegistry &registry) {
-#define GET_CHECKERS
-#define CHECKER(FULLNAME,CLASS,DESCFILE,HELPTEXT,GROUPINDEX,HIDDEN) \
- registry.addChecker(register##CLASS, FULLNAME, HELPTEXT);
-#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
-#undef GET_CHECKERS
-}
diff --git a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h b/lib/StaticAnalyzer/Checkers/ClangSACheckers.h
deleted file mode 100644
index d6e96f27a75e..000000000000
--- a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h
+++ /dev/null
@@ -1,37 +0,0 @@
-//===--- ClangSACheckers.h - Registration functions for Checkers *- C++ -*-===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// Declares the registation functions for the checkers defined in
-// libclangStaticAnalyzerCheckers.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CLANGSACHECKERS_H
-#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CLANGSACHECKERS_H
-
-#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
-
-namespace clang {
-
-namespace ento {
-class CheckerManager;
-class CheckerRegistry;
-
-#define GET_CHECKERS
-#define CHECKER(FULLNAME,CLASS,CXXFILE,HELPTEXT,GROUPINDEX,HIDDEN) \
- void register##CLASS(CheckerManager &mgr);
-#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
-#undef CHECKER
-#undef GET_CHECKERS
-
-} // end ento namespace
-
-} // end clang namespace
-
-#endif
diff --git a/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/lib/StaticAnalyzer/Checkers/CloneChecker.cpp
index ee517ed97770..89354b866004 100644
--- a/lib/StaticAnalyzer/Checkers/CloneChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/CloneChecker.cpp
@@ -13,7 +13,7 @@
///
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Analysis/CloneDetection.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -42,7 +42,7 @@ public:
void reportClones(BugReporter &BR, AnalysisManager &Mgr,
std::vector<CloneDetector::CloneGroup> &CloneGroups) const;
- /// Reports only suspicious clones to the user along with informaton
+ /// Reports only suspicious clones to the user along with information
/// that explain why they are suspicious.
void reportSuspiciousClones(
BugReporter &BR, AnalysisManager &Mgr,
@@ -63,18 +63,18 @@ void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU,
// At this point, every statement in the translation unit has been analyzed by
// the CloneDetector. The only thing left to do is to report the found clones.
- int MinComplexity = Mgr.getAnalyzerOptions().getOptionAsInteger(
+ int MinComplexity = Mgr.getAnalyzerOptions().getCheckerIntegerOption(
"MinimumCloneComplexity", 50, this);
assert(MinComplexity >= 0);
- bool ReportSuspiciousClones = Mgr.getAnalyzerOptions().getBooleanOption(
- "ReportSuspiciousClones", true, this);
+ bool ReportSuspiciousClones = Mgr.getAnalyzerOptions()
+ .getCheckerBooleanOption("ReportSuspiciousClones", true, this);
- bool ReportNormalClones = Mgr.getAnalyzerOptions().getBooleanOption(
+ bool ReportNormalClones = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
"ReportNormalClones", true, this);
- StringRef IgnoredFilesPattern = Mgr.getAnalyzerOptions().getOptionAsString(
- "IgnoredFilesPattern", "", this);
+ StringRef IgnoredFilesPattern = Mgr.getAnalyzerOptions()
+ .getCheckerStringOption("IgnoredFilesPattern", "", this);
// Let the CloneDetector create a list of clones from all the analyzed
// statements. We don't filter for matching variable patterns at this point
diff --git a/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp
index 17ec2c288777..a5c67c2a5b45 100644
--- a/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp
@@ -14,20 +14,25 @@
// of expressions. A warning is reported when:
// * a negative value is implicitly converted to an unsigned value in an
// assignment, comparison or multiplication.
-// * assignment / initialization when source value is greater than the max
-// value of target
+// * assignment / initialization when the source value is greater than the max
+// value of the target integer type
+// * assignment / initialization when the source integer is above the range
+// where the target floating point type can represent all integers
//
// Many compilers and tools have similar checks that are based on semantic
// analysis. Those checks are sound but have poor precision. ConversionChecker
// is an alternative to those checks.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/ADT/APFloat.h"
+
+#include <climits>
using namespace clang;
using namespace ento;
@@ -40,11 +45,9 @@ public:
private:
mutable std::unique_ptr<BuiltinBug> BT;
- // Is there loss of precision
bool isLossOfPrecision(const ImplicitCastExpr *Cast, QualType DestType,
CheckerContext &C) const;
- // Is there loss of sign
bool isLossOfSign(const ImplicitCastExpr *Cast, CheckerContext &C) const;
void reportBug(ExplodedNode *N, CheckerContext &C, const char Msg[]) const;
@@ -132,19 +135,51 @@ bool ConversionChecker::isLossOfPrecision(const ImplicitCastExpr *Cast,
QualType SubType = Cast->IgnoreParenImpCasts()->getType();
- if (!DestType->isIntegerType() || !SubType->isIntegerType())
+ if (!DestType->isRealType() || !SubType->isIntegerType())
return false;
- if (C.getASTContext().getIntWidth(DestType) >=
- C.getASTContext().getIntWidth(SubType))
+ const bool isFloat = DestType->isFloatingType();
+
+ const auto &AC = C.getASTContext();
+
+ // We will find the largest RepresentsUntilExp value such that the DestType
+ // can exactly represent all nonnegative integers below 2^RepresentsUntilExp.
+ unsigned RepresentsUntilExp;
+
+ if (isFloat) {
+ const llvm::fltSemantics &Sema = AC.getFloatTypeSemantics(DestType);
+ RepresentsUntilExp = llvm::APFloat::semanticsPrecision(Sema);
+ } else {
+ RepresentsUntilExp = AC.getIntWidth(DestType);
+ if (RepresentsUntilExp == 1) {
+ // This is just casting a number to bool, probably not a bug.
+ return false;
+ }
+ if (DestType->isSignedIntegerType())
+ RepresentsUntilExp--;
+ }
+
+ if (RepresentsUntilExp >= sizeof(unsigned long long) * CHAR_BIT) {
+ // Avoid overflow in our later calculations.
return false;
+ }
+
+ unsigned CorrectedSrcWidth = AC.getIntWidth(SubType);
+ if (SubType->isSignedIntegerType())
+ CorrectedSrcWidth--;
- unsigned W = C.getASTContext().getIntWidth(DestType);
- if (W == 1 || W >= 64U)
+ if (RepresentsUntilExp >= CorrectedSrcWidth) {
+ // Simple case: the destination can store all values of the source type.
return false;
+ }
- unsigned long long MaxVal = 1ULL << W;
+ unsigned long long MaxVal = 1ULL << RepresentsUntilExp;
+ if (isFloat) {
+ // If this is a floating point type, it can also represent MaxVal exactly.
+ MaxVal++;
+ }
return C.isGreaterOrEqual(Cast->getSubExpr(), MaxVal);
+ // TODO: maybe also check negative values with too large magnitude.
}
bool ConversionChecker::isLossOfSign(const ImplicitCastExpr *Cast,
diff --git a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
index f7b5f61cfb8a..4e0f6d3bedfd 100644
--- a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Attr.h"
#include "clang/AST/ParentMap.h"
@@ -262,7 +262,7 @@ public:
currentBlock = block;
// Skip statements in macros.
- if (S->getLocStart().isMacroID())
+ if (S->getBeginLoc().isMacroID())
return;
// Only cover dead stores from regular assignments. ++/-- dead stores
@@ -329,9 +329,8 @@ public:
return;
if (const Expr *E = V->getInit()) {
- while (const ExprWithCleanups *exprClean =
- dyn_cast<ExprWithCleanups>(E))
- E = exprClean->getSubExpr();
+ while (const FullExpr *FE = dyn_cast<FullExpr>(E))
+ E = FE->getSubExpr();
// Look through transitive assignments, e.g.:
// int x = y = 0;
diff --git a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
index 810a33ed404d..90b1111aff0f 100644
--- a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
+++ b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Analysis/Analyses/Dominators.h"
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/CallGraph.h"
@@ -69,6 +69,25 @@ void ento::registerLiveVariablesDumper(CheckerManager &mgr) {
}
//===----------------------------------------------------------------------===//
+// LiveStatementsDumper
+//===----------------------------------------------------------------------===//
+
+namespace {
+class LiveStatementsDumper : public Checker<check::ASTCodeBody> {
+public:
+ void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr,
+ BugReporter &BR) const {
+ if (LiveVariables *L = Mgr.getAnalysis<RelaxedLiveVariables>(D))
+ L->dumpStmtLiveness(Mgr.getSourceManager());
+ }
+};
+}
+
+void ento::registerLiveStatementsDumper(CheckerManager &mgr) {
+ mgr.registerChecker<LiveStatementsDumper>();
+}
+
+//===----------------------------------------------------------------------===//
// CFGViewer
//===----------------------------------------------------------------------===//
@@ -182,7 +201,9 @@ public:
llvm::errs() << "[config]\n";
for (unsigned I = 0, E = Keys.size(); I != E; ++I)
- llvm::errs() << Keys[I]->getKey() << " = " << Keys[I]->second << '\n';
+ llvm::errs() << Keys[I]->getKey() << " = "
+ << (Keys[I]->second.empty() ? "\"\"" : Keys[I]->second)
+ << '\n';
llvm::errs() << "[stats]\n" << "num-entries = " << Keys.size() << '\n';
}
diff --git a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp
index d3489282ab62..adf5a8e77a74 100644
--- a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp
@@ -21,7 +21,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -47,7 +47,6 @@ class DeleteWithNonVirtualDtorChecker
ID.AddPointer(&X);
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -104,7 +103,7 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE,
std::shared_ptr<PathDiagnosticPiece>
DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode(
- const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
+ const ExplodedNode *N, BugReporterContext &BRC,
BugReport &BR) {
// Stop traversal after the first conversion was found on a path.
if (Satisfied)
diff --git a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
index 152b937bb03f..d01a889d256a 100644
--- a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ExprObjC.h"
#include "clang/AST/ExprOpenMP.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -111,6 +111,12 @@ static bool suppressReport(const Expr *E) {
return E->getType().getQualifiers().hasAddressSpace();
}
+static bool isDeclRefExprToReference(const Expr *E) {
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
+ return DRE->getDecl()->getType()->isReferenceType();
+ return false;
+}
+
void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
CheckerContext &C) const {
// Generate an error node.
@@ -154,7 +160,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
}
case Stmt::MemberExprClass: {
const MemberExpr *M = cast<MemberExpr>(S);
- if (M->isArrow() || bugreporter::isDeclRefExprToReference(M->getBase())) {
+ if (M->isArrow() || isDeclRefExprToReference(M->getBase())) {
os << "Access to field '" << M->getMemberNameInfo()
<< "' results in a dereference of a null pointer";
AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(),
@@ -177,7 +183,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S,
auto report = llvm::make_unique<BugReport>(
*BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N);
- bugreporter::trackNullOrUndefValue(N, bugreporter::getDerefExpr(S), *report);
+ bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report);
for (SmallVectorImpl<SourceRange>::iterator
I = Ranges.begin(), E = Ranges.end(); I!=E; ++I)
@@ -197,8 +203,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S,
auto report =
llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N);
- bugreporter::trackNullOrUndefValue(N, bugreporter::getDerefExpr(S),
- *report);
+ bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report);
C.emitReport(std::move(report));
}
return;
diff --git a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
index 5efb9096f2ff..2a559422df34 100644
--- a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
+++ b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp
@@ -21,7 +21,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/StmtVisitor.h"
diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
index bc39c92ea970..a220a0513e28 100644
--- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -32,6 +32,13 @@ public:
};
} // end anonymous namespace
+static const Expr *getDenomExpr(const ExplodedNode *N) {
+ const Stmt *S = N->getLocationAs<PreStmt>()->getStmt();
+ if (const auto *BE = dyn_cast<BinaryOperator>(S))
+ return BE->getRHS();
+ return nullptr;
+}
+
void DivZeroChecker::reportBug(
const char *Msg, ProgramStateRef StateZero, CheckerContext &C,
std::unique_ptr<BugReporterVisitor> Visitor) const {
@@ -41,7 +48,7 @@ void DivZeroChecker::reportBug(
auto R = llvm::make_unique<BugReport>(*BT, Msg, N);
R->addVisitor(std::move(Visitor));
- bugreporter::trackNullOrUndefValue(N, bugreporter::GetDenomExpr(N), *R);
+ bugreporter::trackExpressionValue(N, getDenomExpr(N), *R);
C.emitReport(std::move(R));
}
}
diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp
index 4e4d81cd6714..803d7ae22a71 100644
--- a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp
@@ -17,7 +17,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -49,7 +49,6 @@ class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> {
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -92,11 +91,10 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType,
std::shared_ptr<PathDiagnosticPiece>
DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
- BugReport &BR) {
+ BugReport &) {
ProgramStateRef State = N->getState();
- ProgramStateRef StatePrev = PrevN->getState();
+ ProgramStateRef StatePrev = N->getFirstPred()->getState();
DynamicTypeInfo TrackedType = getDynamicTypeInfo(State, Reg);
DynamicTypeInfo TrackedTypePrev = getDynamicTypeInfo(StatePrev, Reg);
diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
index 126e57645a43..31d4eebe8968 100644
--- a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
+++ b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp
@@ -21,7 +21,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/Builtins.h"
@@ -85,7 +85,6 @@ class DynamicTypePropagation:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -124,11 +123,6 @@ void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR,
}
}
- if (!SR.hasDeadSymbols()) {
- C.addTransition(State);
- return;
- }
-
MostSpecializedTypeArgsMapTy TyArgMap =
State->get<MostSpecializedTypeArgsMap>();
for (MostSpecializedTypeArgsMapTy::iterator I = TyArgMap.begin(),
@@ -937,11 +931,10 @@ void DynamicTypePropagation::reportGenericsBug(
std::shared_ptr<PathDiagnosticPiece>
DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) {
ProgramStateRef state = N->getState();
- ProgramStateRef statePrev = PrevN->getState();
+ ProgramStateRef statePrev = N->getFirstPred()->getState();
const ObjCObjectPointerType *const *TrackedType =
state->get<MostSpecializedTypeArgsMap>(Sym);
diff --git a/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp
new file mode 100644
index 000000000000..4e51cffaa744
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp
@@ -0,0 +1,128 @@
+//===- EnumCastOutOfRangeChecker.cpp ---------------------------*- C++ -*--===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// The EnumCastOutOfRangeChecker is responsible for checking integer to
+// enumeration casts that could result in undefined values. This could happen
+// if the value that we cast from is out of the value range of the enumeration.
+// Reference:
+// [ISO/IEC 14882-2014] ISO/IEC 14882-2014.
+// Programming Languages — C++, Fourth Edition. 2014.
+// C++ Standard, [dcl.enum], in paragraph 8, which defines the range of an enum
+// C++ Standard, [expr.static.cast], paragraph 10, which defines the behaviour
+// of casting an integer value that is out of range
+// SEI CERT C++ Coding Standard, INT50-CPP. Do not cast to an out-of-range
+// enumeration value
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+// This evaluator checks two SVals for equality. The first SVal is provided via
+// the constructor, the second is the parameter of the overloaded () operator.
+// It uses the in-built ConstraintManager to resolve the equlity to possible or
+// not possible ProgramStates.
+class ConstraintBasedEQEvaluator {
+ const DefinedOrUnknownSVal CompareValue;
+ const ProgramStateRef PS;
+ SValBuilder &SVB;
+
+public:
+ ConstraintBasedEQEvaluator(CheckerContext &C,
+ const DefinedOrUnknownSVal CompareValue)
+ : CompareValue(CompareValue), PS(C.getState()), SVB(C.getSValBuilder()) {}
+
+ bool operator()(const llvm::APSInt &EnumDeclInitValue) {
+ DefinedOrUnknownSVal EnumDeclValue = SVB.makeIntVal(EnumDeclInitValue);
+ DefinedOrUnknownSVal ElemEqualsValueToCast =
+ SVB.evalEQ(PS, EnumDeclValue, CompareValue);
+
+ return static_cast<bool>(PS->assume(ElemEqualsValueToCast, true));
+ }
+};
+
+// This checker checks CastExpr statements.
+// If the value provided to the cast is one of the values the enumeration can
+// represent, the said value matches the enumeration. If the checker can
+// establish the impossibility of matching it gives a warning.
+// Being conservative, it does not warn if there is slight possibility the
+// value can be matching.
+class EnumCastOutOfRangeChecker : public Checker<check::PreStmt<CastExpr>> {
+ mutable std::unique_ptr<BuiltinBug> EnumValueCastOutOfRange;
+ void reportWarning(CheckerContext &C) const;
+
+public:
+ void checkPreStmt(const CastExpr *CE, CheckerContext &C) const;
+};
+
+using EnumValueVector = llvm::SmallVector<llvm::APSInt, 6>;
+
+// Collects all of the values an enum can represent (as SVals).
+EnumValueVector getDeclValuesForEnum(const EnumDecl *ED) {
+ EnumValueVector DeclValues(
+ std::distance(ED->enumerator_begin(), ED->enumerator_end()));
+ llvm::transform(ED->enumerators(), DeclValues.begin(),
+ [](const EnumConstantDecl *D) { return D->getInitVal(); });
+ return DeclValues;
+}
+} // namespace
+
+void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const {
+ if (const ExplodedNode *N = C.generateNonFatalErrorNode()) {
+ if (!EnumValueCastOutOfRange)
+ EnumValueCastOutOfRange.reset(
+ new BuiltinBug(this, "Enum cast out of range",
+ "The value provided to the cast expression is not in "
+ "the valid range of values for the enum"));
+ C.emitReport(llvm::make_unique<BugReport>(
+ *EnumValueCastOutOfRange, EnumValueCastOutOfRange->getDescription(),
+ N));
+ }
+}
+
+void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE,
+ CheckerContext &C) const {
+ // Get the value of the expression to cast.
+ const llvm::Optional<DefinedOrUnknownSVal> ValueToCast =
+ C.getSVal(CE->getSubExpr()).getAs<DefinedOrUnknownSVal>();
+
+ // If the value cannot be reasoned about (not even a DefinedOrUnknownSVal),
+ // don't analyze further.
+ if (!ValueToCast)
+ return;
+
+ const QualType T = CE->getType();
+ // Check whether the cast type is an enum.
+ if (!T->isEnumeralType())
+ return;
+
+ // If the cast is an enum, get its declaration.
+ // If the isEnumeralType() returned true, then the declaration must exist
+ // even if it is a stub declaration. It is up to the getDeclValuesForEnum()
+ // function to handle this.
+ const EnumDecl *ED = T->castAs<EnumType>()->getDecl();
+
+ EnumValueVector DeclValues = getDeclValuesForEnum(ED);
+ // Check if any of the enum values possibly match.
+ bool PossibleValueMatch = llvm::any_of(
+ DeclValues, ConstraintBasedEQEvaluator(C, *ValueToCast));
+
+ // If there is no value that can possibly match any of the enum values, then
+ // warn.
+ if (!PossibleValueMatch)
+ reportWarning(C);
+}
+
+void ento::registerEnumCastOutOfRangeChecker(CheckerManager &mgr) {
+ mgr.registerChecker<EnumCastOutOfRangeChecker>();
+}
diff --git a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
index 8de653c10f7e..2553f54bbcac 100644
--- a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp
@@ -7,7 +7,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Checkers/SValExplainer.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -43,6 +43,8 @@ class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols,
void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const;
void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const;
void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const;
+ void analyzerDenote(const CallExpr *CE, CheckerContext &C) const;
+ void analyzerExpress(const CallExpr *CE, CheckerContext &C) const;
typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *,
CheckerContext &C) const;
@@ -60,6 +62,7 @@ public:
}
REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, SymbolRef)
+REGISTER_MAP_WITH_PROGRAMSTATE(DenotedSymbols, SymbolRef, const StringLiteral *)
bool ExprInspectionChecker::evalCall(const CallExpr *CE,
CheckerContext &C) const {
@@ -82,6 +85,8 @@ bool ExprInspectionChecker::evalCall(const CallExpr *CE,
.Case("clang_analyzer_numTimesReached",
&ExprInspectionChecker::analyzerNumTimesReached)
.Case("clang_analyzer_hashDump", &ExprInspectionChecker::analyzerHashDump)
+ .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote)
+ .Case("clang_analyzer_express", &ExprInspectionChecker::analyzerExpress)
.Default(nullptr);
if (!Handler)
@@ -264,6 +269,13 @@ void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper,
N = BugNode;
State = State->remove<MarkedSymbols>(Sym);
}
+
+ for (auto I : State->get<DenotedSymbols>()) {
+ SymbolRef Sym = I.first;
+ if (!SymReaper.isLive(Sym))
+ State = State->remove<DenotedSymbols>(Sym);
+ }
+
C.addTransition(State, N);
}
@@ -287,7 +299,7 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE,
CheckerContext &C) const {
const LangOptions &Opts = C.getLangOpts();
const SourceManager &SM = C.getSourceManager();
- FullSourceLoc FL(CE->getArg(0)->getLocStart(), SM);
+ FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM);
std::string HashContent =
GetIssueString(SM, FL, getCheckName().getName(), "Category",
C.getLocationContext()->getDecl(), Opts);
@@ -295,6 +307,105 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE,
reportBug(HashContent, C);
}
+void ExprInspectionChecker::analyzerDenote(const CallExpr *CE,
+ CheckerContext &C) const {
+ if (CE->getNumArgs() < 2) {
+ reportBug("clang_analyzer_denote() requires a symbol and a string literal",
+ C);
+ return;
+ }
+
+ SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
+ if (!Sym) {
+ reportBug("Not a symbol", C);
+ return;
+ }
+
+ const auto *E = dyn_cast<StringLiteral>(CE->getArg(1)->IgnoreParenCasts());
+ if (!E) {
+ reportBug("Not a string literal", C);
+ return;
+ }
+
+ ProgramStateRef State = C.getState();
+
+ C.addTransition(C.getState()->set<DenotedSymbols>(Sym, E));
+}
+
+namespace {
+class SymbolExpressor
+ : public SymExprVisitor<SymbolExpressor, Optional<std::string>> {
+ ProgramStateRef State;
+
+public:
+ SymbolExpressor(ProgramStateRef State) : State(State) {}
+
+ Optional<std::string> lookup(const SymExpr *S) {
+ if (const StringLiteral *const *SLPtr = State->get<DenotedSymbols>(S)) {
+ const StringLiteral *SL = *SLPtr;
+ return std::string(SL->getBytes());
+ }
+ return None;
+ }
+
+ Optional<std::string> VisitSymExpr(const SymExpr *S) {
+ return lookup(S);
+ }
+
+ Optional<std::string> VisitSymIntExpr(const SymIntExpr *S) {
+ if (Optional<std::string> Str = lookup(S))
+ return Str;
+ if (Optional<std::string> Str = Visit(S->getLHS()))
+ return (*Str + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " +
+ std::to_string(S->getRHS().getLimitedValue()) +
+ (S->getRHS().isUnsigned() ? "U" : ""))
+ .str();
+ return None;
+ }
+
+ Optional<std::string> VisitSymSymExpr(const SymSymExpr *S) {
+ if (Optional<std::string> Str = lookup(S))
+ return Str;
+ if (Optional<std::string> Str1 = Visit(S->getLHS()))
+ if (Optional<std::string> Str2 = Visit(S->getRHS()))
+ return (*Str1 + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) +
+ " " + *Str2).str();
+ return None;
+ }
+
+ Optional<std::string> VisitSymbolCast(const SymbolCast *S) {
+ if (Optional<std::string> Str = lookup(S))
+ return Str;
+ if (Optional<std::string> Str = Visit(S->getOperand()))
+ return (Twine("(") + S->getType().getAsString() + ")" + *Str).str();
+ return None;
+ }
+};
+} // namespace
+
+void ExprInspectionChecker::analyzerExpress(const CallExpr *CE,
+ CheckerContext &C) const {
+ if (CE->getNumArgs() == 0) {
+ reportBug("clang_analyzer_express() requires a symbol", C);
+ return;
+ }
+
+ SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol();
+ if (!Sym) {
+ reportBug("Not a symbol", C);
+ return;
+ }
+
+ SymbolExpressor V(C.getState());
+ auto Str = V.Visit(Sym);
+ if (!Str) {
+ reportBug("Unable to express", C);
+ return;
+ }
+
+ reportBug(*Str, C);
+}
+
void ento::registerExprInspectionChecker(CheckerManager &Mgr) {
Mgr.registerChecker<ExprInspectionChecker>();
}
diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
index 059203fca730..165a4e4490eb 100644
--- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
index 5cb51b01f044..248b9c3f7693 100644
--- a/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp
@@ -29,7 +29,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
diff --git a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp
index 3ef95e673b87..818716dd6070 100644
--- a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Expr.h"
#include "clang/Basic/LangOptions.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
index 899586745a0b..32fed202d3ab 100644
--- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp
@@ -14,7 +14,7 @@
// aggressively, even if the involved symbols are under constrained.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/Basic/Builtins.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -28,10 +28,13 @@ using namespace clang;
using namespace ento;
namespace {
-class GenericTaintChecker : public Checker< check::PostStmt<CallExpr>,
- check::PreStmt<CallExpr> > {
+class GenericTaintChecker
+ : public Checker<check::PostStmt<CallExpr>, check::PreStmt<CallExpr>> {
public:
- static void *getTag() { static int Tag; return &Tag; }
+ static void *getTag() {
+ static int Tag;
+ return &Tag;
+ }
void checkPostStmt(const CallExpr *CE, CheckerContext &C) const;
@@ -69,8 +72,8 @@ private:
static Optional<SVal> getPointedToSVal(CheckerContext &C, const Expr *Arg);
/// Functions defining the attack surface.
- typedef ProgramStateRef (GenericTaintChecker::*FnCheck)(const CallExpr *,
- CheckerContext &C) const;
+ typedef ProgramStateRef (GenericTaintChecker::*FnCheck)(
+ const CallExpr *, CheckerContext &C) const;
ProgramStateRef postScanf(const CallExpr *CE, CheckerContext &C) const;
ProgramStateRef postSocket(const CallExpr *CE, CheckerContext &C) const;
ProgramStateRef postRetTaint(const CallExpr *CE, CheckerContext &C) const;
@@ -120,16 +123,15 @@ private:
TaintPropagationRule() {}
- TaintPropagationRule(unsigned SArg,
- unsigned DArg, bool TaintRet = false) {
+ TaintPropagationRule(unsigned SArg, unsigned DArg, bool TaintRet = false) {
SrcArgs.push_back(SArg);
DstArgs.push_back(DArg);
if (TaintRet)
DstArgs.push_back(ReturnValueIndex);
}
- TaintPropagationRule(unsigned SArg1, unsigned SArg2,
- unsigned DArg, bool TaintRet = false) {
+ TaintPropagationRule(unsigned SArg1, unsigned SArg2, unsigned DArg,
+ bool TaintRet = false) {
SrcArgs.push_back(SArg1);
SrcArgs.push_back(SArg2);
DstArgs.push_back(DArg);
@@ -139,18 +141,17 @@ private:
/// Get the propagation rule for a given function.
static TaintPropagationRule
- getTaintPropagationRule(const FunctionDecl *FDecl,
- StringRef Name,
- CheckerContext &C);
+ getTaintPropagationRule(const FunctionDecl *FDecl, StringRef Name,
+ CheckerContext &C);
inline void addSrcArg(unsigned A) { SrcArgs.push_back(A); }
- inline void addDstArg(unsigned A) { DstArgs.push_back(A); }
+ inline void addDstArg(unsigned A) { DstArgs.push_back(A); }
inline bool isNull() const { return SrcArgs.empty(); }
inline bool isDestinationArgument(unsigned ArgNum) const {
- return (std::find(DstArgs.begin(),
- DstArgs.end(), ArgNum) != DstArgs.end());
+ return (std::find(DstArgs.begin(), DstArgs.end(), ArgNum) !=
+ DstArgs.end());
}
static inline bool isTaintedOrPointsToTainted(const Expr *E,
@@ -169,7 +170,6 @@ private:
/// Pre-process a function which propagates taint according to the
/// taint rule.
ProgramStateRef process(const CallExpr *CE, CheckerContext &C) const;
-
};
};
@@ -177,17 +177,18 @@ const unsigned GenericTaintChecker::ReturnValueIndex;
const unsigned GenericTaintChecker::InvalidArgIndex;
const char GenericTaintChecker::MsgUncontrolledFormatString[] =
- "Untrusted data is used as a format string "
- "(CWE-134: Uncontrolled Format String)";
+ "Untrusted data is used as a format string "
+ "(CWE-134: Uncontrolled Format String)";
const char GenericTaintChecker::MsgSanitizeSystemArgs[] =
- "Untrusted data is passed to a system call "
- "(CERT/STR02-C. Sanitize data passed to complex subsystems)";
+ "Untrusted data is passed to a system call "
+ "(CERT/STR02-C. Sanitize data passed to complex subsystems)";
const char GenericTaintChecker::MsgTaintedBufferSize[] =
- "Untrusted data is used to specify the buffer size "
- "(CERT/STR31-C. Guarantee that storage for strings has sufficient space for "
- "character data and the null terminator)";
+ "Untrusted data is used to specify the buffer size "
+ "(CERT/STR31-C. Guarantee that storage for strings has sufficient space "
+ "for "
+ "character data and the null terminator)";
} // end of anonymous namespace
@@ -199,33 +200,32 @@ REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned)
GenericTaintChecker::TaintPropagationRule
GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule(
- const FunctionDecl *FDecl,
- StringRef Name,
- CheckerContext &C) {
+ const FunctionDecl *FDecl, StringRef Name, CheckerContext &C) {
// TODO: Currently, we might lose precision here: we always mark a return
// value as tainted even if it's just a pointer, pointing to tainted data.
// Check for exact name match for functions without builtin substitutes.
- TaintPropagationRule Rule = llvm::StringSwitch<TaintPropagationRule>(Name)
- .Case("atoi", TaintPropagationRule(0, ReturnValueIndex))
- .Case("atol", TaintPropagationRule(0, ReturnValueIndex))
- .Case("atoll", TaintPropagationRule(0, ReturnValueIndex))
- .Case("getc", TaintPropagationRule(0, ReturnValueIndex))
- .Case("fgetc", TaintPropagationRule(0, ReturnValueIndex))
- .Case("getc_unlocked", TaintPropagationRule(0, ReturnValueIndex))
- .Case("getw", TaintPropagationRule(0, ReturnValueIndex))
- .Case("toupper", TaintPropagationRule(0, ReturnValueIndex))
- .Case("tolower", TaintPropagationRule(0, ReturnValueIndex))
- .Case("strchr", TaintPropagationRule(0, ReturnValueIndex))
- .Case("strrchr", TaintPropagationRule(0, ReturnValueIndex))
- .Case("read", TaintPropagationRule(0, 2, 1, true))
- .Case("pread", TaintPropagationRule(InvalidArgIndex, 1, true))
- .Case("gets", TaintPropagationRule(InvalidArgIndex, 0, true))
- .Case("fgets", TaintPropagationRule(2, 0, true))
- .Case("getline", TaintPropagationRule(2, 0))
- .Case("getdelim", TaintPropagationRule(3, 0))
- .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex))
- .Default(TaintPropagationRule());
+ TaintPropagationRule Rule =
+ llvm::StringSwitch<TaintPropagationRule>(Name)
+ .Case("atoi", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("atol", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("atoll", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("getc", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("fgetc", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("getc_unlocked", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("getw", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("toupper", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("tolower", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("strchr", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("strrchr", TaintPropagationRule(0, ReturnValueIndex))
+ .Case("read", TaintPropagationRule(0, 2, 1, true))
+ .Case("pread", TaintPropagationRule(InvalidArgIndex, 1, true))
+ .Case("gets", TaintPropagationRule(InvalidArgIndex, 0, true))
+ .Case("fgets", TaintPropagationRule(2, 0, true))
+ .Case("getline", TaintPropagationRule(2, 0))
+ .Case("getdelim", TaintPropagationRule(3, 0))
+ .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex))
+ .Default(TaintPropagationRule());
if (!Rule.isNull())
return Rule;
@@ -233,8 +233,8 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule(
// Check if it's one of the memory setting/copying functions.
// This check is specialized but faster then calling isCLibraryFunction.
unsigned BId = 0;
- if ( (BId = FDecl->getMemoryFunctionKind()) )
- switch(BId) {
+ if ((BId = FDecl->getMemoryFunctionKind()))
+ switch (BId) {
case Builtin::BImemcpy:
case Builtin::BImemmove:
case Builtin::BIstrncpy:
@@ -305,7 +305,7 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE,
// First, try generating a propagation rule for this function.
TaintPropagationRule Rule =
- TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C);
+ TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C);
if (!Rule.isNull()) {
State = Rule.process(CE, C);
if (!State)
@@ -316,15 +316,14 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE,
// Otherwise, check if we have custom pre-processing implemented.
FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name)
- .Case("fscanf", &GenericTaintChecker::preFscanf)
- .Default(nullptr);
+ .Case("fscanf", &GenericTaintChecker::preFscanf)
+ .Default(nullptr);
// Check and evaluate the call.
if (evalFunction)
State = (this->*evalFunction)(CE, C);
if (!State)
return;
C.addTransition(State);
-
}
bool GenericTaintChecker::propagateFromPre(const CallExpr *CE,
@@ -338,9 +337,10 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE,
if (TaintArgs.isEmpty())
return false;
- for (llvm::ImmutableSet<unsigned>::iterator
- I = TaintArgs.begin(), E = TaintArgs.end(); I != E; ++I) {
- unsigned ArgNum = *I;
+ for (llvm::ImmutableSet<unsigned>::iterator I = TaintArgs.begin(),
+ E = TaintArgs.end();
+ I != E; ++I) {
+ unsigned ArgNum = *I;
// Special handling for the tainted return value.
if (ArgNum == ReturnValueIndex) {
@@ -352,7 +352,7 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE,
// tainted after the call.
if (CE->getNumArgs() < (ArgNum + 1))
return false;
- const Expr* Arg = CE->getArg(ArgNum);
+ const Expr *Arg = CE->getArg(ArgNum);
Optional<SVal> V = getPointedToSVal(C, Arg);
if (V)
State = State->addTaint(*V);
@@ -379,19 +379,20 @@ void GenericTaintChecker::addSourcesPost(const CallExpr *CE,
StringRef Name = C.getCalleeName(FDecl);
if (Name.empty())
return;
- FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name)
- .Case("scanf", &GenericTaintChecker::postScanf)
- // TODO: Add support for vfscanf & family.
- .Case("getchar", &GenericTaintChecker::postRetTaint)
- .Case("getchar_unlocked", &GenericTaintChecker::postRetTaint)
- .Case("getenv", &GenericTaintChecker::postRetTaint)
- .Case("fopen", &GenericTaintChecker::postRetTaint)
- .Case("fdopen", &GenericTaintChecker::postRetTaint)
- .Case("freopen", &GenericTaintChecker::postRetTaint)
- .Case("getch", &GenericTaintChecker::postRetTaint)
- .Case("wgetch", &GenericTaintChecker::postRetTaint)
- .Case("socket", &GenericTaintChecker::postSocket)
- .Default(nullptr);
+ FnCheck evalFunction =
+ llvm::StringSwitch<FnCheck>(Name)
+ .Case("scanf", &GenericTaintChecker::postScanf)
+ // TODO: Add support for vfscanf & family.
+ .Case("getchar", &GenericTaintChecker::postRetTaint)
+ .Case("getchar_unlocked", &GenericTaintChecker::postRetTaint)
+ .Case("getenv", &GenericTaintChecker::postRetTaint)
+ .Case("fopen", &GenericTaintChecker::postRetTaint)
+ .Case("fdopen", &GenericTaintChecker::postRetTaint)
+ .Case("freopen", &GenericTaintChecker::postRetTaint)
+ .Case("getch", &GenericTaintChecker::postRetTaint)
+ .Case("wgetch", &GenericTaintChecker::postRetTaint)
+ .Case("socket", &GenericTaintChecker::postSocket)
+ .Default(nullptr);
// If the callee isn't defined, it is not of security concern.
// Check and evaluate the call.
@@ -404,7 +405,8 @@ void GenericTaintChecker::addSourcesPost(const CallExpr *CE,
C.addTransition(State);
}
-bool GenericTaintChecker::checkPre(const CallExpr *CE, CheckerContext &C) const{
+bool GenericTaintChecker::checkPre(const CallExpr *CE,
+ CheckerContext &C) const {
if (checkUncontrolledFormatString(CE, C))
return true;
@@ -458,8 +460,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE,
// Check for taint in arguments.
bool IsTainted = false;
- for (ArgVector::const_iterator I = SrcArgs.begin(),
- E = SrcArgs.end(); I != E; ++I) {
+ for (ArgVector::const_iterator I = SrcArgs.begin(), E = SrcArgs.end(); I != E;
+ ++I) {
unsigned ArgNum = *I;
if (ArgNum == InvalidArgIndex) {
@@ -483,8 +485,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE,
return State;
// Mark the arguments which should be tainted after the function returns.
- for (ArgVector::const_iterator I = DstArgs.begin(),
- E = DstArgs.end(); I != E; ++I) {
+ for (ArgVector::const_iterator I = DstArgs.begin(), E = DstArgs.end(); I != E;
+ ++I) {
unsigned ArgNum = *I;
// Should we mark all arguments as tainted?
@@ -498,8 +500,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE,
// Process pointer argument.
const Type *ArgTy = Arg->getType().getTypePtr();
QualType PType = ArgTy->getPointeeType();
- if ((!PType.isNull() && !PType.isConstQualified())
- || (ArgTy->isReferenceType() && !Arg->getType().isConstQualified()))
+ if ((!PType.isNull() && !PType.isConstQualified()) ||
+ (ArgTy->isReferenceType() && !Arg->getType().isConstQualified()))
State = State->add<TaintArgsOnPostVisit>(i);
}
continue;
@@ -519,11 +521,10 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE,
return State;
}
-
// If argument 0 (file descriptor) is tainted, all arguments except for arg 0
// and arg 1 should get taint.
ProgramStateRef GenericTaintChecker::preFscanf(const CallExpr *CE,
- CheckerContext &C) const {
+ CheckerContext &C) const {
assert(CE->getNumArgs() >= 2);
ProgramStateRef State = C.getState();
@@ -532,14 +533,13 @@ ProgramStateRef GenericTaintChecker::preFscanf(const CallExpr *CE,
isStdin(CE->getArg(0), C)) {
// All arguments except for the first two should get taint.
for (unsigned int i = 2; i < CE->getNumArgs(); ++i)
- State = State->add<TaintArgsOnPostVisit>(i);
+ State = State->add<TaintArgsOnPostVisit>(i);
return State;
}
return nullptr;
}
-
// If argument 0(protocol domain) is network, the return value should get taint.
ProgramStateRef GenericTaintChecker::postSocket(const CallExpr *CE,
CheckerContext &C) const {
@@ -558,7 +558,7 @@ ProgramStateRef GenericTaintChecker::postSocket(const CallExpr *CE,
}
ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE,
- CheckerContext &C) const {
+ CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (CE->getNumArgs() < 2)
return State;
@@ -567,7 +567,7 @@ ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE,
for (unsigned int i = 1; i < CE->getNumArgs(); ++i) {
// The arguments are pointer arguments. The data they are pointing at is
// tainted after the call.
- const Expr* Arg = CE->getArg(i);
+ const Expr *Arg = CE->getArg(i);
Optional<SVal> V = getPointedToSVal(C, Arg);
if (V)
State = State->addTaint(*V);
@@ -593,7 +593,8 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) {
return false;
// Get it's symbol and find the declaration region it's pointing to.
- const SymbolRegionValue *Sm =dyn_cast<SymbolRegionValue>(SymReg->getSymbol());
+ const SymbolRegionValue *Sm =
+ dyn_cast<SymbolRegionValue>(SymReg->getSymbol());
if (!Sm)
return false;
const DeclRegion *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion());
@@ -605,11 +606,11 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) {
if (const VarDecl *D = dyn_cast_or_null<VarDecl>(DeclReg->getDecl())) {
D = D->getCanonicalDecl();
if ((D->getName().find("stdin") != StringRef::npos) && D->isExternC())
- if (const PointerType * PtrTy =
+ if (const PointerType *PtrTy =
dyn_cast<PointerType>(D->getType().getTypePtr()))
- if (PtrTy->getPointeeType().getCanonicalType() ==
- C.getASTContext().getFILEType().getCanonicalType())
- return true;
+ if (PtrTy->getPointeeType().getCanonicalType() ==
+ C.getASTContext().getFILEType().getCanonicalType())
+ return true;
}
return false;
}
@@ -625,8 +626,7 @@ static bool getPrintfFormatArgumentNum(const CallExpr *CE,
return false;
for (const auto *Format : FDecl->specific_attrs<FormatAttr>()) {
ArgNum = Format->getFormatIdx() - 1;
- if ((Format->getType()->getName() == "printf") &&
- CE->getNumArgs() > ArgNum)
+ if ((Format->getType()->getName() == "printf") && CE->getNumArgs() > ArgNum)
return true;
}
@@ -667,36 +667,36 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E,
return false;
}
-bool GenericTaintChecker::checkUncontrolledFormatString(const CallExpr *CE,
- CheckerContext &C) const{
+bool GenericTaintChecker::checkUncontrolledFormatString(
+ const CallExpr *CE, CheckerContext &C) const {
// Check if the function contains a format string argument.
unsigned int ArgNum = 0;
if (!getPrintfFormatArgumentNum(CE, C, ArgNum))
return false;
- // If either the format string content or the pointer itself are tainted, warn.
+ // If either the format string content or the pointer itself are tainted,
+ // warn.
return generateReportIfTainted(CE->getArg(ArgNum),
MsgUncontrolledFormatString, C);
}
-bool GenericTaintChecker::checkSystemCall(const CallExpr *CE,
- StringRef Name,
+bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name,
CheckerContext &C) const {
// TODO: It might make sense to run this check on demand. In some cases,
// we should check if the environment has been cleansed here. We also might
// need to know if the user was reset before these calls(seteuid).
unsigned ArgNum = llvm::StringSwitch<unsigned>(Name)
- .Case("system", 0)
- .Case("popen", 0)
- .Case("execl", 0)
- .Case("execle", 0)
- .Case("execlp", 0)
- .Case("execv", 0)
- .Case("execvp", 0)
- .Case("execvP", 0)
- .Case("execve", 0)
- .Case("dlopen", 0)
- .Default(UINT_MAX);
+ .Case("system", 0)
+ .Case("popen", 0)
+ .Case("execl", 0)
+ .Case("execle", 0)
+ .Case("execlp", 0)
+ .Case("execv", 0)
+ .Case("execvp", 0)
+ .Case("execvP", 0)
+ .Case("execve", 0)
+ .Case("dlopen", 0)
+ .Default(UINT_MAX);
if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1))
return false;
@@ -712,8 +712,8 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE,
// If the function has a buffer size argument, set ArgNum.
unsigned ArgNum = InvalidArgIndex;
unsigned BId = 0;
- if ( (BId = FDecl->getMemoryFunctionKind()) )
- switch(BId) {
+ if ((BId = FDecl->getMemoryFunctionKind()))
+ switch (BId) {
case Builtin::BImemcpy:
case Builtin::BImemmove:
case Builtin::BIstrncpy:
diff --git a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
index f102ca96a5c1..4c2a229428d9 100644
--- a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp
@@ -16,7 +16,7 @@
///
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp
index 29677f737f5c..a4f47d727a8f 100644
--- a/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp
@@ -14,7 +14,8 @@
//===----------------------------------------------------------------------===//
#include "AllocationState.h"
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "InterCheckerAPI.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -24,23 +25,10 @@
using namespace clang;
using namespace ento;
-using PtrSet = llvm::ImmutableSet<SymbolRef>;
-
// Associate container objects with a set of raw pointer symbols.
+REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(PtrSet, SymbolRef)
REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet)
-// This is a trick to gain access to PtrSet's Factory.
-namespace clang {
-namespace ento {
-template <>
-struct ProgramStateTrait<PtrSet> : public ProgramStatePartialTrait<PtrSet> {
- static void *GDMIndex() {
- static int Index = 0;
- return &Index;
- }
-};
-} // end namespace ento
-} // end namespace clang
namespace {
@@ -67,8 +55,7 @@ public:
ID.AddPointer(getTag());
}
- std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
+ virtual std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -85,14 +72,20 @@ public:
};
InnerPointerChecker()
- : AppendFn("append"), AssignFn("assign"), ClearFn("clear"),
- CStrFn("c_str"), DataFn("data"), EraseFn("erase"), InsertFn("insert"),
- PopBackFn("pop_back"), PushBackFn("push_back"), ReplaceFn("replace"),
- ReserveFn("reserve"), ResizeFn("resize"),
- ShrinkToFitFn("shrink_to_fit"), SwapFn("swap") {}
-
- /// Check if the object of this member function call is a `basic_string`.
- bool isCalledOnStringObject(const CXXInstanceCall *ICall) const;
+ : AppendFn({"std", "basic_string", "append"}),
+ AssignFn({"std", "basic_string", "assign"}),
+ ClearFn({"std", "basic_string", "clear"}),
+ CStrFn({"std", "basic_string", "c_str"}),
+ DataFn({"std", "basic_string", "data"}),
+ EraseFn({"std", "basic_string", "erase"}),
+ InsertFn({"std", "basic_string", "insert"}),
+ PopBackFn({"std", "basic_string", "pop_back"}),
+ PushBackFn({"std", "basic_string", "push_back"}),
+ ReplaceFn({"std", "basic_string", "replace"}),
+ ReserveFn({"std", "basic_string", "reserve"}),
+ ResizeFn({"std", "basic_string", "resize"}),
+ ShrinkToFitFn({"std", "basic_string", "shrink_to_fit"}),
+ SwapFn({"std", "basic_string", "swap"}) {}
/// Check whether the called member function potentially invalidates
/// pointers referring to the container object's inner buffer.
@@ -121,21 +114,6 @@ public:
} // end anonymous namespace
-bool InnerPointerChecker::isCalledOnStringObject(
- const CXXInstanceCall *ICall) const {
- const auto *ObjRegion =
- dyn_cast_or_null<TypedValueRegion>(ICall->getCXXThisVal().getAsRegion());
- if (!ObjRegion)
- return false;
-
- QualType ObjTy = ObjRegion->getValueType();
- if (ObjTy.isNull() ||
- ObjTy->getAsCXXRecordDecl()->getName() != "basic_string")
- return false;
-
- return true;
-}
-
bool InnerPointerChecker::isInvalidatingMemberFunction(
const CallEvent &Call) const {
if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) {
@@ -219,33 +197,34 @@ void InnerPointerChecker::checkPostCall(const CallEvent &Call,
ProgramStateRef State = C.getState();
if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) {
- if (isCalledOnStringObject(ICall)) {
- const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>(
- ICall->getCXXThisVal().getAsRegion());
-
- if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) {
- SVal RawPtr = Call.getReturnValue();
- if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) {
- // Start tracking this raw pointer by adding it to the set of symbols
- // associated with this container object in the program state map.
-
- PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
- const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion);
- PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet();
- assert(C.wasInlined || !Set.contains(Sym));
- Set = F.add(Set, Sym);
-
- State = State->set<RawPtrMap>(ObjRegion, Set);
- C.addTransition(State);
- }
- return;
- }
+ // TODO: Do we need these to be typed?
+ const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>(
+ ICall->getCXXThisVal().getAsRegion());
+ if (!ObjRegion)
+ return;
+
+ if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) {
+ SVal RawPtr = Call.getReturnValue();
+ if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) {
+ // Start tracking this raw pointer by adding it to the set of symbols
+ // associated with this container object in the program state map.
- // Check [string.require] / second point.
- if (isInvalidatingMemberFunction(Call)) {
- markPtrSymbolsReleased(Call, State, ObjRegion, C);
- return;
+ PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>();
+ const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion);
+ PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet();
+ assert(C.wasInlined || !Set.contains(Sym));
+ Set = F.add(Set, Sym);
+
+ State = State->set<RawPtrMap>(ObjRegion, Set);
+ C.addTransition(State);
}
+ return;
+ }
+
+ // Check [string.require] / second point.
+ if (isInvalidatingMemberFunction(Call)) {
+ markPtrSymbolsReleased(Call, State, ObjRegion, C);
+ return;
}
}
@@ -278,41 +257,56 @@ void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper,
C.addTransition(State);
}
+namespace clang {
+namespace ento {
+namespace allocation_state {
+
+std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) {
+ return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym);
+}
+
+const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) {
+ RawPtrMapTy Map = State->get<RawPtrMap>();
+ for (const auto Entry : Map) {
+ if (Entry.second.contains(Sym)) {
+ return Entry.first;
+ }
+ }
+ return nullptr;
+}
+
+} // end namespace allocation_state
+} // end namespace ento
+} // end namespace clang
+
std::shared_ptr<PathDiagnosticPiece>
InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
- BugReport &BR) {
+ BugReport &) {
if (!isSymbolTracked(N->getState(), PtrToBuf) ||
- isSymbolTracked(PrevN->getState(), PtrToBuf))
+ isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf))
return nullptr;
const Stmt *S = PathDiagnosticLocation::getStmt(N);
if (!S)
return nullptr;
+ const MemRegion *ObjRegion =
+ allocation_state::getContainerObjRegion(N->getState(), PtrToBuf);
+ const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
+ QualType ObjTy = TypedRegion->getValueType();
+
SmallString<256> Buf;
llvm::raw_svector_ostream OS(Buf);
- OS << "Dangling inner pointer obtained here";
+ OS << "Pointer to inner buffer of '" << ObjTy.getAsString()
+ << "' obtained here";
PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
N->getLocationContext());
return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true,
nullptr);
}
-namespace clang {
-namespace ento {
-namespace allocation_state {
-
-std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) {
- return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym);
-}
-
-} // end namespace allocation_state
-} // end namespace ento
-} // end namespace clang
-
void ento::registerInnerPointerChecker(CheckerManager &Mgr) {
- registerNewDeleteChecker(Mgr);
+ registerInnerPointerCheckerAux(Mgr);
Mgr.registerChecker<InnerPointerChecker>();
}
diff --git a/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h
index d38d63cd05ce..81c95a4813a6 100644
--- a/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h
+++ b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h
@@ -20,5 +20,8 @@ namespace ento {
/// Register the checker which evaluates CString API calls.
void registerCStringCheckerBasic(CheckerManager &Mgr);
+/// Register the part of MallocChecker connected to InnerPointerChecker.
+void registerInnerPointerCheckerAux(CheckerManager &Mgr);
+
}}
#endif /* INTERCHECKERAPI_H_ */
diff --git a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp
index 520c32e1c770..e719e19d68e9 100644
--- a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp
@@ -66,11 +66,15 @@
// making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as
// a constraint which we later retrieve when doing an actual comparison.
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/AST/DeclTemplate.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
+
+#include <utility>
using namespace clang;
using namespace ento;
@@ -85,34 +89,47 @@ private:
// Container the iterator belongs to
const MemRegion *Cont;
+ // Whether iterator is valid
+ const bool Valid;
+
// Abstract offset
const SymbolRef Offset;
- IteratorPosition(const MemRegion *C, SymbolRef Of)
- : Cont(C), Offset(Of) {}
+ IteratorPosition(const MemRegion *C, bool V, SymbolRef Of)
+ : Cont(C), Valid(V), Offset(Of) {}
public:
const MemRegion *getContainer() const { return Cont; }
+ bool isValid() const { return Valid; }
SymbolRef getOffset() const { return Offset; }
+ IteratorPosition invalidate() const {
+ return IteratorPosition(Cont, false, Offset);
+ }
+
static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) {
- return IteratorPosition(C, Of);
+ return IteratorPosition(C, true, Of);
}
IteratorPosition setTo(SymbolRef NewOf) const {
- return IteratorPosition(Cont, NewOf);
+ return IteratorPosition(Cont, Valid, NewOf);
+ }
+
+ IteratorPosition reAssign(const MemRegion *NewCont) const {
+ return IteratorPosition(NewCont, Valid, Offset);
}
bool operator==(const IteratorPosition &X) const {
- return Cont == X.Cont && Offset == X.Offset;
+ return Cont == X.Cont && Valid == X.Valid && Offset == X.Offset;
}
bool operator!=(const IteratorPosition &X) const {
- return Cont != X.Cont || Offset != X.Offset;
+ return Cont != X.Cont || Valid != X.Valid || Offset != X.Offset;
}
void Profile(llvm::FoldingSetNodeID &ID) const {
ID.AddPointer(Cont);
+ ID.AddInteger(Valid);
ID.Add(Offset);
}
};
@@ -181,15 +198,17 @@ public:
class IteratorChecker
: public Checker<check::PreCall, check::PostCall,
- check::PreStmt<CXXOperatorCallExpr>,
- check::PostStmt<MaterializeTemporaryExpr>,
+ check::PostStmt<MaterializeTemporaryExpr>, check::Bind,
check::LiveSymbols, check::DeadSymbols,
eval::Assume> {
std::unique_ptr<BugType> OutOfRangeBugType;
+ std::unique_ptr<BugType> MismatchedBugType;
+ std::unique_ptr<BugType> InvalidatedBugType;
void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal,
const SVal &RVal, OverloadedOperatorKind Op) const;
+ void verifyAccess(CheckerContext &C, const SVal &Val) const;
void verifyDereference(CheckerContext &C, const SVal &Val) const;
void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter,
bool Postfix) const;
@@ -204,17 +223,50 @@ class IteratorChecker
const SVal &Cont) const;
void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal,
const MemRegion *Cont) const;
+ void handleAssign(CheckerContext &C, const SVal &Cont,
+ const Expr *CE = nullptr,
+ const SVal &OldCont = UndefinedVal()) const;
+ void handleClear(CheckerContext &C, const SVal &Cont) const;
+ void handlePushBack(CheckerContext &C, const SVal &Cont) const;
+ void handlePopBack(CheckerContext &C, const SVal &Cont) const;
+ void handlePushFront(CheckerContext &C, const SVal &Cont) const;
+ void handlePopFront(CheckerContext &C, const SVal &Cont) const;
+ void handleInsert(CheckerContext &C, const SVal &Iter) const;
+ void handleErase(CheckerContext &C, const SVal &Iter) const;
+ void handleErase(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const;
+ void handleEraseAfter(CheckerContext &C, const SVal &Iter) const;
+ void handleEraseAfter(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const;
+ void verifyIncrement(CheckerContext &C, const SVal &Iter) const;
+ void verifyDecrement(CheckerContext &C, const SVal &Iter) const;
void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op,
- const SVal &RetVal, const SVal &LHS,
- const SVal &RHS) const;
+ const SVal &LHS, const SVal &RHS) const;
+ void verifyMatch(CheckerContext &C, const SVal &Iter,
+ const MemRegion *Cont) const;
+ void verifyMatch(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const;
+ IteratorPosition advancePosition(CheckerContext &C, OverloadedOperatorKind Op,
+ const IteratorPosition &Pos,
+ const SVal &Distance) const;
void reportOutOfRangeBug(const StringRef &Message, const SVal &Val,
CheckerContext &C, ExplodedNode *ErrNode) const;
+ void reportMismatchedBug(const StringRef &Message, const SVal &Val1,
+ const SVal &Val2, CheckerContext &C,
+ ExplodedNode *ErrNode) const;
+ void reportMismatchedBug(const StringRef &Message, const SVal &Val,
+ const MemRegion *Reg, CheckerContext &C,
+ ExplodedNode *ErrNode) const;
+ void reportInvalidatedBug(const StringRef &Message, const SVal &Val,
+ CheckerContext &C, ExplodedNode *ErrNode) const;
public:
IteratorChecker();
enum CheckKind {
CK_IteratorRangeChecker,
+ CK_MismatchedIteratorChecker,
+ CK_InvalidatedIteratorChecker,
CK_NumCheckKinds
};
@@ -223,7 +275,9 @@ public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
- void checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const;
+ void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const;
+ void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const;
+ void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const;
void checkPostStmt(const MaterializeTemporaryExpr *MTE,
CheckerContext &C) const;
void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
@@ -246,13 +300,31 @@ namespace {
bool isIteratorType(const QualType &Type);
bool isIterator(const CXXRecordDecl *CRD);
+bool isComparisonOperator(OverloadedOperatorKind OK);
bool isBeginCall(const FunctionDecl *Func);
bool isEndCall(const FunctionDecl *Func);
+bool isAssignCall(const FunctionDecl *Func);
+bool isClearCall(const FunctionDecl *Func);
+bool isPushBackCall(const FunctionDecl *Func);
+bool isEmplaceBackCall(const FunctionDecl *Func);
+bool isPopBackCall(const FunctionDecl *Func);
+bool isPushFrontCall(const FunctionDecl *Func);
+bool isEmplaceFrontCall(const FunctionDecl *Func);
+bool isPopFrontCall(const FunctionDecl *Func);
+bool isInsertCall(const FunctionDecl *Func);
+bool isEraseCall(const FunctionDecl *Func);
+bool isEraseAfterCall(const FunctionDecl *Func);
+bool isEmplaceCall(const FunctionDecl *Func);
+bool isAssignmentOperator(OverloadedOperatorKind OK);
bool isSimpleComparisonOperator(OverloadedOperatorKind OK);
+bool isAccessOperator(OverloadedOperatorKind OK);
bool isDereferenceOperator(OverloadedOperatorKind OK);
bool isIncrementOperator(OverloadedOperatorKind OK);
bool isDecrementOperator(OverloadedOperatorKind OK);
bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK);
+bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg);
+bool frontModifiable(ProgramStateRef State, const MemRegion *Reg);
+bool backModifiable(ProgramStateRef State, const MemRegion *Reg);
BinaryOperator::Opcode getOpcode(const SymExpr *SE);
const RegionOrSymbol getRegionOrSymbol(const SVal &Val);
const ProgramStateRef processComparison(ProgramStateRef State,
@@ -287,12 +359,41 @@ ProgramStateRef relateIteratorPositions(ProgramStateRef State,
const IteratorPosition &Pos1,
const IteratorPosition &Pos2,
bool Equal);
+ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State,
+ const MemRegion *Cont);
+ProgramStateRef
+invalidateAllIteratorPositionsExcept(ProgramStateRef State,
+ const MemRegion *Cont, SymbolRef Offset,
+ BinaryOperator::Opcode Opc);
+ProgramStateRef invalidateIteratorPositions(ProgramStateRef State,
+ SymbolRef Offset,
+ BinaryOperator::Opcode Opc);
+ProgramStateRef invalidateIteratorPositions(ProgramStateRef State,
+ SymbolRef Offset1,
+ BinaryOperator::Opcode Opc1,
+ SymbolRef Offset2,
+ BinaryOperator::Opcode Opc2);
+ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State,
+ const MemRegion *Cont,
+ const MemRegion *NewCont);
+ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State,
+ const MemRegion *Cont,
+ const MemRegion *NewCont,
+ SymbolRef Offset,
+ BinaryOperator::Opcode Opc);
+ProgramStateRef rebaseSymbolInIteratorPositionsIf(
+ ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym,
+ SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc);
const ContainerData *getContainerData(ProgramStateRef State,
const MemRegion *Cont);
ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont,
const ContainerData &CData);
bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont);
-bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos);
+bool isBoundThroughLazyCompoundVal(const Environment &Env,
+ const MemRegion *Reg);
+bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos);
+bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos);
+bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos);
bool isZero(ProgramStateRef State, const NonLoc &Val);
} // namespace
@@ -300,39 +401,198 @@ IteratorChecker::IteratorChecker() {
OutOfRangeBugType.reset(
new BugType(this, "Iterator out of range", "Misuse of STL APIs"));
OutOfRangeBugType->setSuppressOnSink(true);
+ MismatchedBugType.reset(
+ new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs"));
+ MismatchedBugType->setSuppressOnSink(true);
+ InvalidatedBugType.reset(
+ new BugType(this, "Iterator invalidated", "Misuse of STL APIs"));
+ InvalidatedBugType->setSuppressOnSink(true);
}
void IteratorChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
- // Check for out of range access
+ // Check for out of range access or access of invalidated position and
+ // iterator mismatches
const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
if (!Func)
return;
if (Func->isOverloadedOperator()) {
- if (ChecksEnabled[CK_IteratorRangeChecker] &&
- isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) {
+ if (ChecksEnabled[CK_InvalidatedIteratorChecker] &&
+ isAccessOperator(Func->getOverloadedOperator())) {
+ // Check for any kind of access of invalidated iterator positions
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
- // Check for out-of-range incrementions and decrementions
- if (Call.getNumArgs() >= 1) {
- verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(),
- Call.getReturnValue(),
- InstCall->getCXXThisVal(), Call.getArgSVal(0));
- }
+ verifyAccess(C, InstCall->getCXXThisVal());
} else {
- if (Call.getNumArgs() >= 2) {
- verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(),
- Call.getReturnValue(), Call.getArgSVal(0),
- Call.getArgSVal(1));
+ verifyAccess(C, Call.getArgSVal(0));
+ }
+ }
+ if (ChecksEnabled[CK_IteratorRangeChecker]) {
+ if (isIncrementOperator(Func->getOverloadedOperator())) {
+ // Check for out-of-range incrementions
+ if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ verifyIncrement(C, InstCall->getCXXThisVal());
+ } else {
+ if (Call.getNumArgs() >= 1) {
+ verifyIncrement(C, Call.getArgSVal(0));
+ }
+ }
+ } else if (isDecrementOperator(Func->getOverloadedOperator())) {
+ // Check for out-of-range decrementions
+ if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ verifyDecrement(C, InstCall->getCXXThisVal());
+ } else {
+ if (Call.getNumArgs() >= 1) {
+ verifyDecrement(C, Call.getArgSVal(0));
+ }
+ }
+ } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) {
+ if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ // Check for out-of-range incrementions and decrementions
+ if (Call.getNumArgs() >= 1) {
+ verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(),
+ InstCall->getCXXThisVal(),
+ Call.getArgSVal(0));
+ }
+ } else {
+ if (Call.getNumArgs() >= 2) {
+ verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(),
+ Call.getArgSVal(0), Call.getArgSVal(1));
+ }
+ }
+ } else if (isDereferenceOperator(Func->getOverloadedOperator())) {
+ // Check for dereference of out-of-range iterators
+ if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ verifyDereference(C, InstCall->getCXXThisVal());
+ } else {
+ verifyDereference(C, Call.getArgSVal(0));
}
}
- } else if (ChecksEnabled[CK_IteratorRangeChecker] &&
- isDereferenceOperator(Func->getOverloadedOperator())) {
- // Check for dereference of out-of-range iterators
+ } else if (ChecksEnabled[CK_MismatchedIteratorChecker] &&
+ isComparisonOperator(Func->getOverloadedOperator())) {
+ // Check for comparisons of iterators of different containers
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
- verifyDereference(C, InstCall->getCXXThisVal());
+ if (Call.getNumArgs() < 1)
+ return;
+
+ if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) ||
+ !isIteratorType(Call.getArgExpr(0)->getType()))
+ return;
+
+ verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0));
} else {
- verifyDereference(C, Call.getArgSVal(0));
+ if (Call.getNumArgs() < 2)
+ return;
+
+ if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
+ !isIteratorType(Call.getArgExpr(1)->getType()))
+ return;
+
+ verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
+ }
+ }
+ } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (!ChecksEnabled[CK_MismatchedIteratorChecker])
+ return;
+
+ const auto *ContReg = InstCall->getCXXThisVal().getAsRegion();
+ if (!ContReg)
+ return;
+ // Check for erase, insert and emplace using iterator of another container
+ if (isEraseCall(Func) || isEraseAfterCall(Func)) {
+ verifyMatch(C, Call.getArgSVal(0),
+ InstCall->getCXXThisVal().getAsRegion());
+ if (Call.getNumArgs() == 2) {
+ verifyMatch(C, Call.getArgSVal(1),
+ InstCall->getCXXThisVal().getAsRegion());
+ }
+ } else if (isInsertCall(Func)) {
+ verifyMatch(C, Call.getArgSVal(0),
+ InstCall->getCXXThisVal().getAsRegion());
+ if (Call.getNumArgs() == 3 &&
+ isIteratorType(Call.getArgExpr(1)->getType()) &&
+ isIteratorType(Call.getArgExpr(2)->getType())) {
+ verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2));
+ }
+ } else if (isEmplaceCall(Func)) {
+ verifyMatch(C, Call.getArgSVal(0),
+ InstCall->getCXXThisVal().getAsRegion());
+ }
+ } else if (isa<CXXConstructorCall>(&Call)) {
+ // Check match of first-last iterator pair in a constructor of a container
+ if (Call.getNumArgs() < 2)
+ return;
+
+ const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl());
+ if (Ctr->getNumParams() < 2)
+ return;
+
+ if (Ctr->getParamDecl(0)->getName() != "first" ||
+ Ctr->getParamDecl(1)->getName() != "last")
+ return;
+
+ if (!isIteratorType(Call.getArgExpr(0)->getType()) ||
+ !isIteratorType(Call.getArgExpr(1)->getType()))
+ return;
+
+ verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1));
+ } else {
+ // The main purpose of iterators is to abstract away from different
+ // containers and provide a (maybe limited) uniform access to them.
+ // This implies that any correctly written template function that
+ // works on multiple containers using iterators takes different
+ // template parameters for different containers. So we can safely
+ // assume that passing iterators of different containers as arguments
+ // whose type replaces the same template parameter is a bug.
+ //
+ // Example:
+ // template<typename I1, typename I2>
+ // void f(I1 first1, I1 last1, I2 first2, I2 last2);
+ //
+ // In this case the first two arguments to f() must be iterators must belong
+ // to the same container and the last to also to the same container but
+ // not necessarily to the same as the first two.
+
+ if (!ChecksEnabled[CK_MismatchedIteratorChecker])
+ return;
+
+ const auto *Templ = Func->getPrimaryTemplate();
+ if (!Templ)
+ return;
+
+ const auto *TParams = Templ->getTemplateParameters();
+ const auto *TArgs = Func->getTemplateSpecializationArgs();
+
+ // Iterate over all the template parameters
+ for (size_t I = 0; I < TParams->size(); ++I) {
+ const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I));
+ if (!TPDecl)
+ continue;
+
+ if (TPDecl->isParameterPack())
+ continue;
+
+ const auto TAType = TArgs->get(I).getAsType();
+ if (!isIteratorType(TAType))
+ continue;
+
+ SVal LHS = UndefinedVal();
+
+ // For every template parameter which is an iterator type in the
+ // instantiation look for all functions' parameters' type by it and
+ // check whether they belong to the same container
+ for (auto J = 0U; J < Func->getNumParams(); ++J) {
+ const auto *Param = Func->getParamDecl(J);
+ const auto *ParamType =
+ Param->getType()->getAs<SubstTemplateTypeParmType>();
+ if (!ParamType ||
+ ParamType->getReplacedParameter()->getDecl() != TPDecl)
+ continue;
+ if (LHS.isUndef()) {
+ LHS = Call.getArgSVal(J);
+ } else {
+ verifyMatch(C, LHS, Call.getArgSVal(J));
+ }
}
}
}
@@ -347,7 +607,15 @@ void IteratorChecker::checkPostCall(const CallEvent &Call,
if (Func->isOverloadedOperator()) {
const auto Op = Func->getOverloadedOperator();
- if (isSimpleComparisonOperator(Op)) {
+ if (isAssignmentOperator(Op)) {
+ const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call);
+ if (Func->getParamDecl(0)->getType()->isRValueReferenceType()) {
+ handleAssign(C, InstCall->getCXXThisVal(), Call.getOriginExpr(),
+ Call.getArgSVal(0));
+ } else {
+ handleAssign(C, InstCall->getCXXThisVal());
+ }
+ } else if (isSimpleComparisonOperator(Op)) {
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
handleComparison(C, Call.getReturnValue(), InstCall->getCXXThisVal(),
Call.getArgSVal(0), Op);
@@ -387,6 +655,36 @@ void IteratorChecker::checkPostCall(const CallEvent &Call,
}
}
} else {
+ if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
+ if (isAssignCall(Func)) {
+ handleAssign(C, InstCall->getCXXThisVal());
+ } else if (isClearCall(Func)) {
+ handleClear(C, InstCall->getCXXThisVal());
+ } else if (isPushBackCall(Func) || isEmplaceBackCall(Func)) {
+ handlePushBack(C, InstCall->getCXXThisVal());
+ } else if (isPopBackCall(Func)) {
+ handlePopBack(C, InstCall->getCXXThisVal());
+ } else if (isPushFrontCall(Func) || isEmplaceFrontCall(Func)) {
+ handlePushFront(C, InstCall->getCXXThisVal());
+ } else if (isPopFrontCall(Func)) {
+ handlePopFront(C, InstCall->getCXXThisVal());
+ } else if (isInsertCall(Func) || isEmplaceCall(Func)) {
+ handleInsert(C, Call.getArgSVal(0));
+ } else if (isEraseCall(Func)) {
+ if (Call.getNumArgs() == 1) {
+ handleErase(C, Call.getArgSVal(0));
+ } else if (Call.getNumArgs() == 2) {
+ handleErase(C, Call.getArgSVal(0), Call.getArgSVal(1));
+ }
+ } else if (isEraseAfterCall(Func)) {
+ if (Call.getNumArgs() == 1) {
+ handleEraseAfter(C, Call.getArgSVal(0));
+ } else if (Call.getNumArgs() == 2) {
+ handleEraseAfter(C, Call.getArgSVal(0), Call.getArgSVal(1));
+ }
+ }
+ }
+
const auto *OrigExpr = Call.getOriginExpr();
if (!OrigExpr)
return;
@@ -395,9 +693,6 @@ void IteratorChecker::checkPostCall(const CallEvent &Call,
return;
auto State = C.getState();
- // Already bound to container?
- if (getIteratorPosition(State, Call.getReturnValue()))
- return;
if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) {
if (isBeginCall(Func)) {
@@ -412,6 +707,10 @@ void IteratorChecker::checkPostCall(const CallEvent &Call,
}
}
+ // Already bound to container?
+ if (getIteratorPosition(State, Call.getReturnValue()))
+ return;
+
// Copy-like and move constructors
if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) {
if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) {
@@ -441,33 +740,19 @@ void IteratorChecker::checkPostCall(const CallEvent &Call,
}
}
-void IteratorChecker::checkPreStmt(const CXXOperatorCallExpr *COCE,
- CheckerContext &C) const {
- const auto *ThisExpr = COCE->getArg(0);
-
+void IteratorChecker::checkBind(SVal Loc, SVal Val, const Stmt *S,
+ CheckerContext &C) const {
auto State = C.getState();
- const auto *LCtx = C.getLocationContext();
-
- const auto CurrentThis = State->getSVal(ThisExpr, LCtx);
- if (const auto *Reg = CurrentThis.getAsRegion()) {
- if (!Reg->getAs<CXXTempObjectRegion>())
- return;
- const auto OldState = C.getPredecessor()->getFirstPred()->getState();
- const auto OldThis = OldState->getSVal(ThisExpr, LCtx);
- // FIXME: This solution is unreliable. It may happen that another checker
- // subscribes to the pre-statement check of `CXXOperatorCallExpr`
- // and adds a transition before us. The proper fix is to make the
- // CFG provide a `ConstructionContext` for the `CXXOperatorCallExpr`,
- // which would turn the corresponding `CFGStmt` element into a
- // `CFGCXXRecordTypedCall` element, which will allow `ExprEngine` to
- // foresee that the `begin()`/`end()` call constructs the object
- // directly in the temporary region that `CXXOperatorCallExpr` takes
- // as its implicit object argument.
- const auto *Pos = getIteratorPosition(OldState, OldThis);
- if (!Pos)
- return;
- State = setIteratorPosition(State, CurrentThis, *Pos);
+ const auto *Pos = getIteratorPosition(State, Val);
+ if (Pos) {
+ State = setIteratorPosition(State, Loc, *Pos);
C.addTransition(State);
+ } else {
+ const auto *OldPos = getIteratorPosition(State, Loc);
+ if (OldPos) {
+ State = removeIteratorPosition(State, Loc);
+ C.addTransition(State);
+ }
}
}
@@ -508,9 +793,13 @@ void IteratorChecker::checkLiveSymbols(ProgramStateRef State,
const auto CData = Cont.second;
if (CData.getBegin()) {
SR.markLive(CData.getBegin());
+ if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getBegin()))
+ SR.markLive(SIE->getLHS());
}
if (CData.getEnd()) {
SR.markLive(CData.getEnd());
+ if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getEnd()))
+ SR.markLive(SIE->getLHS());
}
}
}
@@ -523,7 +812,12 @@ void IteratorChecker::checkDeadSymbols(SymbolReaper &SR,
auto RegionMap = State->get<IteratorRegionMap>();
for (const auto Reg : RegionMap) {
if (!SR.isLiveRegion(Reg.first)) {
- State = State->remove<IteratorRegionMap>(Reg.first);
+ // The region behind the `LazyCompoundVal` is often cleaned up before
+ // the `LazyCompoundVal` itself. If there are iterator positions keyed
+ // by these regions their cleanup must be deferred.
+ if (!isBoundThroughLazyCompoundVal(State->getEnvironment(), Reg.first)) {
+ State = State->remove<IteratorRegionMap>(Reg.first);
+ }
}
}
@@ -623,14 +917,24 @@ void IteratorChecker::verifyDereference(CheckerContext &C,
const SVal &Val) const {
auto State = C.getState();
const auto *Pos = getIteratorPosition(State, Val);
- if (Pos && isOutOfRange(State, *Pos)) {
- // If I do not put a tag here, some range tests will fail
- static CheckerProgramPointTag Tag("IteratorRangeChecker",
- "IteratorOutOfRange");
- auto *N = C.generateNonFatalErrorNode(State, &Tag);
+ if (Pos && isPastTheEnd(State, *Pos)) {
+ auto *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
- reportOutOfRangeBug("Iterator accessed outside of its range.", Val, C, N);
+ reportOutOfRangeBug("Past-the-end iterator dereferenced.", Val, C, N);
+ return;
+ }
+}
+
+void IteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const {
+ auto State = C.getState();
+ const auto *Pos = getIteratorPosition(State, Val);
+ if (Pos && !Pos->isValid()) {
+ auto *N = C.generateNonFatalErrorNode(State);
+ if (!N) {
+ return;
+ }
+ reportInvalidatedBug("Invalidated iterator accessed.", Val, C, N);
}
}
@@ -643,14 +947,9 @@ void IteratorChecker::handleIncrement(CheckerContext &C, const SVal &RetVal,
if (Pos) {
auto &SymMgr = C.getSymbolManager();
auto &BVF = SymMgr.getBasicVals();
- auto &SVB = C.getSValBuilder();
- const auto OldOffset = Pos->getOffset();
- auto NewOffset =
- SVB.evalBinOp(State, BO_Add,
- nonloc::SymbolVal(OldOffset),
- nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
- SymMgr.getType(OldOffset)).getAsSymbol();
- auto NewPos = Pos->setTo(NewOffset);
+ const auto NewPos =
+ advancePosition(C, OO_Plus, *Pos,
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
State = setIteratorPosition(State, Iter, NewPos);
State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos);
C.addTransition(State);
@@ -666,14 +965,9 @@ void IteratorChecker::handleDecrement(CheckerContext &C, const SVal &RetVal,
if (Pos) {
auto &SymMgr = C.getSymbolManager();
auto &BVF = SymMgr.getBasicVals();
- auto &SVB = C.getSValBuilder();
- const auto OldOffset = Pos->getOffset();
- auto NewOffset =
- SVB.evalBinOp(State, BO_Sub,
- nonloc::SymbolVal(OldOffset),
- nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
- SymMgr.getType(OldOffset)).getAsSymbol();
- auto NewPos = Pos->setTo(NewOffset);
+ const auto NewPos =
+ advancePosition(C, OO_Minus, *Pos,
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
State = setIteratorPosition(State, Iter, NewPos);
State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos);
C.addTransition(State);
@@ -739,69 +1033,95 @@ void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C,
value = &val;
}
- auto &SymMgr = C.getSymbolManager();
- auto &SVB = C.getSValBuilder();
- auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub;
- const auto OldOffset = Pos->getOffset();
- SymbolRef NewOffset;
- if (const auto intValue = value->getAs<nonloc::ConcreteInt>()) {
- // For concrete integers we can calculate the new position
- NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset),
- *intValue,
- SymMgr.getType(OldOffset)).getAsSymbol();
- } else {
- // For other symbols create a new symbol to keep expressions simple
- const auto &LCtx = C.getLocationContext();
- NewOffset = SymMgr.conjureSymbol(nullptr, LCtx, SymMgr.getType(OldOffset),
- C.blockCount());
- State = assumeNoOverflow(State, NewOffset, 4);
- }
- auto NewPos = Pos->setTo(NewOffset);
auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal;
- State = setIteratorPosition(State, TgtVal, NewPos);
+ State =
+ setIteratorPosition(State, TgtVal, advancePosition(C, Op, *Pos, *value));
C.addTransition(State);
}
+void IteratorChecker::verifyIncrement(CheckerContext &C,
+ const SVal &Iter) const {
+ auto &BVF = C.getSValBuilder().getBasicValueFactory();
+ verifyRandomIncrOrDecr(C, OO_Plus, Iter,
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
+}
+
+void IteratorChecker::verifyDecrement(CheckerContext &C,
+ const SVal &Iter) const {
+ auto &BVF = C.getSValBuilder().getBasicValueFactory();
+ verifyRandomIncrOrDecr(C, OO_Minus, Iter,
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))));
+}
+
void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C,
OverloadedOperatorKind Op,
- const SVal &RetVal,
const SVal &LHS,
const SVal &RHS) const {
auto State = C.getState();
// If the iterator is initially inside its range, then the operation is valid
const auto *Pos = getIteratorPosition(State, LHS);
- if (!Pos || !isOutOfRange(State, *Pos))
+ if (!Pos)
return;
- auto value = RHS;
- if (auto loc = RHS.getAs<Loc>()) {
- value = State->getRawSVal(*loc);
+ auto Value = RHS;
+ if (auto ValAsLoc = RHS.getAs<Loc>()) {
+ Value = State->getRawSVal(*ValAsLoc);
}
- // Incremention or decremention by 0 is never bug
- if (isZero(State, value.castAs<NonLoc>()))
+ if (Value.isUnknown())
return;
- auto &SymMgr = C.getSymbolManager();
- auto &SVB = C.getSValBuilder();
- auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub;
- const auto OldOffset = Pos->getOffset();
- const auto intValue = value.getAs<nonloc::ConcreteInt>();
- if (!intValue)
+ // Incremention or decremention by 0 is never a bug.
+ if (isZero(State, Value.castAs<NonLoc>()))
return;
- auto NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset),
- *intValue,
- SymMgr.getType(OldOffset)).getAsSymbol();
- auto NewPos = Pos->setTo(NewOffset);
+ // The result may be the past-end iterator of the container, but any other
+ // out of range position is undefined behaviour
+ if (isAheadOfRange(State, advancePosition(C, Op, *Pos, Value))) {
+ auto *N = C.generateNonFatalErrorNode(State);
+ if (!N)
+ return;
+ reportOutOfRangeBug("Iterator decremented ahead of its valid range.", LHS,
+ C, N);
+ }
+ if (isBehindPastTheEnd(State, advancePosition(C, Op, *Pos, Value))) {
+ auto *N = C.generateNonFatalErrorNode(State);
+ if (!N)
+ return;
+ reportOutOfRangeBug("Iterator incremented behind the past-the-end "
+ "iterator.", LHS, C, N);
+ }
+}
+
+void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter,
+ const MemRegion *Cont) const {
+ // Verify match between a container and the container of an iterator
+ Cont = Cont->getMostDerivedObjectRegion();
+
+ auto State = C.getState();
+ const auto *Pos = getIteratorPosition(State, Iter);
+ if (Pos && Pos->getContainer() != Cont) {
+ auto *N = C.generateNonFatalErrorNode(State);
+ if (!N) {
+ return;
+ }
+ reportMismatchedBug("Container accessed using foreign iterator argument.", Iter, Cont, C, N);
+ }
+}
- // If out of range, the only valid operation is to step into the range
- if (isOutOfRange(State, NewPos)) {
+void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const {
+ // Verify match between the containers of two iterators
+ auto State = C.getState();
+ const auto *Pos1 = getIteratorPosition(State, Iter1);
+ const auto *Pos2 = getIteratorPosition(State, Iter2);
+ if (Pos1 && Pos2 && Pos1->getContainer() != Pos2->getContainer()) {
auto *N = C.generateNonFatalErrorNode(State);
if (!N)
return;
- reportOutOfRangeBug("Iterator accessed past its end.", LHS, C, N);
+ reportMismatchedBug("Iterators of different containers used where the "
+ "same container is expected.", Iter1, Iter2, C, N);
}
}
@@ -811,9 +1131,7 @@ void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE,
if (!ContReg)
return;
- while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) {
- ContReg = CBOR->getSuperRegion();
- }
+ ContReg = ContReg->getMostDerivedObjectRegion();
// If the container already has a begin symbol then use it. Otherwise first
// create a new one.
@@ -837,9 +1155,7 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE,
if (!ContReg)
return;
- while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) {
- ContReg = CBOR->getSuperRegion();
- }
+ ContReg = ContReg->getMostDerivedObjectRegion();
// If the container already has an end symbol then use it. Otherwise first
// create a new one.
@@ -860,9 +1176,7 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE,
void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE,
const SVal &RetVal,
const MemRegion *Cont) const {
- while (const auto *CBOR = Cont->getAs<CXXBaseObjectRegion>()) {
- Cont = CBOR->getSuperRegion();
- }
+ Cont = Cont->getMostDerivedObjectRegion();
auto State = C.getState();
auto &SymMgr = C.getSymbolManager();
@@ -874,6 +1188,399 @@ void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE,
C.addTransition(State);
}
+void IteratorChecker::handleAssign(CheckerContext &C, const SVal &Cont,
+ const Expr *CE, const SVal &OldCont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ // Assignment of a new value to a container always invalidates all its
+ // iterators
+ auto State = C.getState();
+ const auto CData = getContainerData(State, ContReg);
+ if (CData) {
+ State = invalidateAllIteratorPositions(State, ContReg);
+ }
+
+ // In case of move, iterators of the old container (except the past-end
+ // iterators) remain valid but refer to the new container
+ if (!OldCont.isUndef()) {
+ const auto *OldContReg = OldCont.getAsRegion();
+ if (OldContReg) {
+ OldContReg = OldContReg->getMostDerivedObjectRegion();
+ const auto OldCData = getContainerData(State, OldContReg);
+ if (OldCData) {
+ if (const auto OldEndSym = OldCData->getEnd()) {
+ // If we already assigned an "end" symbol to the old container, then
+ // first reassign all iterator positions to the new container which
+ // are not past the container (thus not greater or equal to the
+ // current "end" symbol).
+ State = reassignAllIteratorPositionsUnless(State, OldContReg, ContReg,
+ OldEndSym, BO_GE);
+ auto &SymMgr = C.getSymbolManager();
+ auto &SVB = C.getSValBuilder();
+ // Then generate and assign a new "end" symbol for the new container.
+ auto NewEndSym =
+ SymMgr.conjureSymbol(CE, C.getLocationContext(),
+ C.getASTContext().LongTy, C.blockCount());
+ State = assumeNoOverflow(State, NewEndSym, 4);
+ if (CData) {
+ State = setContainerData(State, ContReg, CData->newEnd(NewEndSym));
+ } else {
+ State = setContainerData(State, ContReg,
+ ContainerData::fromEnd(NewEndSym));
+ }
+ // Finally, replace the old "end" symbol in the already reassigned
+ // iterator positions with the new "end" symbol.
+ State = rebaseSymbolInIteratorPositionsIf(
+ State, SVB, OldEndSym, NewEndSym, OldEndSym, BO_LT);
+ } else {
+ // There was no "end" symbol assigned yet to the old container,
+ // so reassign all iterator positions to the new container.
+ State = reassignAllIteratorPositions(State, OldContReg, ContReg);
+ }
+ if (const auto OldBeginSym = OldCData->getBegin()) {
+ // If we already assigned a "begin" symbol to the old container, then
+ // assign it to the new container and remove it from the old one.
+ if (CData) {
+ State =
+ setContainerData(State, ContReg, CData->newBegin(OldBeginSym));
+ } else {
+ State = setContainerData(State, ContReg,
+ ContainerData::fromBegin(OldBeginSym));
+ }
+ State =
+ setContainerData(State, OldContReg, OldCData->newEnd(nullptr));
+ }
+ } else {
+ // There was neither "begin" nor "end" symbol assigned yet to the old
+ // container, so reassign all iterator positions to the new container.
+ State = reassignAllIteratorPositions(State, OldContReg, ContReg);
+ }
+ }
+ }
+ C.addTransition(State);
+}
+
+void IteratorChecker::handleClear(CheckerContext &C, const SVal &Cont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ // The clear() operation invalidates all the iterators, except the past-end
+ // iterators of list-like containers
+ auto State = C.getState();
+ if (!hasSubscriptOperator(State, ContReg) ||
+ !backModifiable(State, ContReg)) {
+ const auto CData = getContainerData(State, ContReg);
+ if (CData) {
+ if (const auto EndSym = CData->getEnd()) {
+ State =
+ invalidateAllIteratorPositionsExcept(State, ContReg, EndSym, BO_GE);
+ C.addTransition(State);
+ return;
+ }
+ }
+ }
+ State = invalidateAllIteratorPositions(State, ContReg);
+ C.addTransition(State);
+}
+
+void IteratorChecker::handlePushBack(CheckerContext &C,
+ const SVal &Cont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ // For deque-like containers invalidate all iterator positions
+ auto State = C.getState();
+ if (hasSubscriptOperator(State, ContReg) && frontModifiable(State, ContReg)) {
+ State = invalidateAllIteratorPositions(State, ContReg);
+ C.addTransition(State);
+ return;
+ }
+
+ const auto CData = getContainerData(State, ContReg);
+ if (!CData)
+ return;
+
+ // For vector-like containers invalidate the past-end iterator positions
+ if (const auto EndSym = CData->getEnd()) {
+ if (hasSubscriptOperator(State, ContReg)) {
+ State = invalidateIteratorPositions(State, EndSym, BO_GE);
+ }
+ auto &SymMgr = C.getSymbolManager();
+ auto &BVF = SymMgr.getBasicVals();
+ auto &SVB = C.getSValBuilder();
+ const auto newEndSym =
+ SVB.evalBinOp(State, BO_Add,
+ nonloc::SymbolVal(EndSym),
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
+ SymMgr.getType(EndSym)).getAsSymbol();
+ State = setContainerData(State, ContReg, CData->newEnd(newEndSym));
+ }
+ C.addTransition(State);
+}
+
+void IteratorChecker::handlePopBack(CheckerContext &C, const SVal &Cont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ auto State = C.getState();
+ const auto CData = getContainerData(State, ContReg);
+ if (!CData)
+ return;
+
+ if (const auto EndSym = CData->getEnd()) {
+ auto &SymMgr = C.getSymbolManager();
+ auto &BVF = SymMgr.getBasicVals();
+ auto &SVB = C.getSValBuilder();
+ const auto BackSym =
+ SVB.evalBinOp(State, BO_Sub,
+ nonloc::SymbolVal(EndSym),
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
+ SymMgr.getType(EndSym)).getAsSymbol();
+ // For vector-like and deque-like containers invalidate the last and the
+ // past-end iterator positions. For list-like containers only invalidate
+ // the last position
+ if (hasSubscriptOperator(State, ContReg) &&
+ backModifiable(State, ContReg)) {
+ State = invalidateIteratorPositions(State, BackSym, BO_GE);
+ State = setContainerData(State, ContReg, CData->newEnd(nullptr));
+ } else {
+ State = invalidateIteratorPositions(State, BackSym, BO_EQ);
+ }
+ auto newEndSym = BackSym;
+ State = setContainerData(State, ContReg, CData->newEnd(newEndSym));
+ C.addTransition(State);
+ }
+}
+
+void IteratorChecker::handlePushFront(CheckerContext &C,
+ const SVal &Cont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ // For deque-like containers invalidate all iterator positions
+ auto State = C.getState();
+ if (hasSubscriptOperator(State, ContReg)) {
+ State = invalidateAllIteratorPositions(State, ContReg);
+ C.addTransition(State);
+ } else {
+ const auto CData = getContainerData(State, ContReg);
+ if (!CData)
+ return;
+
+ if (const auto BeginSym = CData->getBegin()) {
+ auto &SymMgr = C.getSymbolManager();
+ auto &BVF = SymMgr.getBasicVals();
+ auto &SVB = C.getSValBuilder();
+ const auto newBeginSym =
+ SVB.evalBinOp(State, BO_Sub,
+ nonloc::SymbolVal(BeginSym),
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
+ SymMgr.getType(BeginSym)).getAsSymbol();
+ State = setContainerData(State, ContReg, CData->newBegin(newBeginSym));
+ C.addTransition(State);
+ }
+ }
+}
+
+void IteratorChecker::handlePopFront(CheckerContext &C,
+ const SVal &Cont) const {
+ const auto *ContReg = Cont.getAsRegion();
+ if (!ContReg)
+ return;
+
+ ContReg = ContReg->getMostDerivedObjectRegion();
+
+ auto State = C.getState();
+ const auto CData = getContainerData(State, ContReg);
+ if (!CData)
+ return;
+
+ // For deque-like containers invalidate all iterator positions. For list-like
+ // iterators only invalidate the first position
+ if (const auto BeginSym = CData->getBegin()) {
+ if (hasSubscriptOperator(State, ContReg)) {
+ State = invalidateIteratorPositions(State, BeginSym, BO_LE);
+ } else {
+ State = invalidateIteratorPositions(State, BeginSym, BO_EQ);
+ }
+ auto &SymMgr = C.getSymbolManager();
+ auto &BVF = SymMgr.getBasicVals();
+ auto &SVB = C.getSValBuilder();
+ const auto newBeginSym =
+ SVB.evalBinOp(State, BO_Add,
+ nonloc::SymbolVal(BeginSym),
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
+ SymMgr.getType(BeginSym)).getAsSymbol();
+ State = setContainerData(State, ContReg, CData->newBegin(newBeginSym));
+ C.addTransition(State);
+ }
+}
+
+void IteratorChecker::handleInsert(CheckerContext &C, const SVal &Iter) const {
+ auto State = C.getState();
+ const auto *Pos = getIteratorPosition(State, Iter);
+ if (!Pos)
+ return;
+
+ // For deque-like containers invalidate all iterator positions. For
+ // vector-like containers invalidate iterator positions after the insertion.
+ const auto *Cont = Pos->getContainer();
+ if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) {
+ if (frontModifiable(State, Cont)) {
+ State = invalidateAllIteratorPositions(State, Cont);
+ } else {
+ State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE);
+ }
+ if (const auto *CData = getContainerData(State, Cont)) {
+ if (const auto EndSym = CData->getEnd()) {
+ State = invalidateIteratorPositions(State, EndSym, BO_GE);
+ State = setContainerData(State, Cont, CData->newEnd(nullptr));
+ }
+ }
+ C.addTransition(State);
+ }
+}
+
+void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter) const {
+ auto State = C.getState();
+ const auto *Pos = getIteratorPosition(State, Iter);
+ if (!Pos)
+ return;
+
+ // For deque-like containers invalidate all iterator positions. For
+ // vector-like containers invalidate iterator positions at and after the
+ // deletion. For list-like containers only invalidate the deleted position.
+ const auto *Cont = Pos->getContainer();
+ if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) {
+ if (frontModifiable(State, Cont)) {
+ State = invalidateAllIteratorPositions(State, Cont);
+ } else {
+ State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE);
+ }
+ if (const auto *CData = getContainerData(State, Cont)) {
+ if (const auto EndSym = CData->getEnd()) {
+ State = invalidateIteratorPositions(State, EndSym, BO_GE);
+ State = setContainerData(State, Cont, CData->newEnd(nullptr));
+ }
+ }
+ } else {
+ State = invalidateIteratorPositions(State, Pos->getOffset(), BO_EQ);
+ }
+ C.addTransition(State);
+}
+
+void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const {
+ auto State = C.getState();
+ const auto *Pos1 = getIteratorPosition(State, Iter1);
+ const auto *Pos2 = getIteratorPosition(State, Iter2);
+ if (!Pos1 || !Pos2)
+ return;
+
+ // For deque-like containers invalidate all iterator positions. For
+ // vector-like containers invalidate iterator positions at and after the
+ // deletion range. For list-like containers only invalidate the deleted
+ // position range [first..last].
+ const auto *Cont = Pos1->getContainer();
+ if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) {
+ if (frontModifiable(State, Cont)) {
+ State = invalidateAllIteratorPositions(State, Cont);
+ } else {
+ State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE);
+ }
+ if (const auto *CData = getContainerData(State, Cont)) {
+ if (const auto EndSym = CData->getEnd()) {
+ State = invalidateIteratorPositions(State, EndSym, BO_GE);
+ State = setContainerData(State, Cont, CData->newEnd(nullptr));
+ }
+ }
+ } else {
+ State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE,
+ Pos2->getOffset(), BO_LT);
+ }
+ C.addTransition(State);
+}
+
+void IteratorChecker::handleEraseAfter(CheckerContext &C,
+ const SVal &Iter) const {
+ auto State = C.getState();
+ const auto *Pos = getIteratorPosition(State, Iter);
+ if (!Pos)
+ return;
+
+ // Invalidate the deleted iterator position, which is the position of the
+ // parameter plus one.
+ auto &SymMgr = C.getSymbolManager();
+ auto &BVF = SymMgr.getBasicVals();
+ auto &SVB = C.getSValBuilder();
+ const auto NextSym =
+ SVB.evalBinOp(State, BO_Add,
+ nonloc::SymbolVal(Pos->getOffset()),
+ nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))),
+ SymMgr.getType(Pos->getOffset())).getAsSymbol();
+ State = invalidateIteratorPositions(State, NextSym, BO_EQ);
+ C.addTransition(State);
+}
+
+void IteratorChecker::handleEraseAfter(CheckerContext &C, const SVal &Iter1,
+ const SVal &Iter2) const {
+ auto State = C.getState();
+ const auto *Pos1 = getIteratorPosition(State, Iter1);
+ const auto *Pos2 = getIteratorPosition(State, Iter2);
+ if (!Pos1 || !Pos2)
+ return;
+
+ // Invalidate the deleted iterator position range (first..last)
+ State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GT,
+ Pos2->getOffset(), BO_LT);
+ C.addTransition(State);
+}
+
+IteratorPosition IteratorChecker::advancePosition(CheckerContext &C,
+ OverloadedOperatorKind Op,
+ const IteratorPosition &Pos,
+ const SVal &Distance) const {
+ auto State = C.getState();
+ auto &SymMgr = C.getSymbolManager();
+ auto &SVB = C.getSValBuilder();
+
+ assert ((Op == OO_Plus || Op == OO_PlusEqual ||
+ Op == OO_Minus || Op == OO_MinusEqual) &&
+ "Advance operator must be one of +, -, += and -=.");
+ auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub;
+ if (const auto IntDist = Distance.getAs<nonloc::ConcreteInt>()) {
+ // For concrete integers we can calculate the new position
+ return Pos.setTo(SVB.evalBinOp(State, BinOp,
+ nonloc::SymbolVal(Pos.getOffset()), *IntDist,
+ SymMgr.getType(Pos.getOffset()))
+ .getAsSymbol());
+ } else {
+ // For other symbols create a new symbol to keep expressions simple
+ const auto &LCtx = C.getLocationContext();
+ const auto NewPosSym = SymMgr.conjureSymbol(nullptr, LCtx,
+ SymMgr.getType(Pos.getOffset()),
+ C.blockCount());
+ State = assumeNoOverflow(State, NewPosSym, 4);
+ return Pos.setTo(NewPosSym);
+ }
+}
+
void IteratorChecker::reportOutOfRangeBug(const StringRef &Message,
const SVal &Val, CheckerContext &C,
ExplodedNode *ErrNode) const {
@@ -882,14 +1589,47 @@ void IteratorChecker::reportOutOfRangeBug(const StringRef &Message,
C.emitReport(std::move(R));
}
+void IteratorChecker::reportMismatchedBug(const StringRef &Message,
+ const SVal &Val1, const SVal &Val2,
+ CheckerContext &C,
+ ExplodedNode *ErrNode) const {
+ auto R = llvm::make_unique<BugReport>(*MismatchedBugType, Message, ErrNode);
+ R->markInteresting(Val1);
+ R->markInteresting(Val2);
+ C.emitReport(std::move(R));
+}
+
+void IteratorChecker::reportMismatchedBug(const StringRef &Message,
+ const SVal &Val, const MemRegion *Reg,
+ CheckerContext &C,
+ ExplodedNode *ErrNode) const {
+ auto R = llvm::make_unique<BugReport>(*MismatchedBugType, Message, ErrNode);
+ R->markInteresting(Val);
+ R->markInteresting(Reg);
+ C.emitReport(std::move(R));
+}
+
+void IteratorChecker::reportInvalidatedBug(const StringRef &Message,
+ const SVal &Val, CheckerContext &C,
+ ExplodedNode *ErrNode) const {
+ auto R = llvm::make_unique<BugReport>(*InvalidatedBugType, Message, ErrNode);
+ R->markInteresting(Val);
+ C.emitReport(std::move(R));
+}
+
namespace {
bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2);
-bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2);
+bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2);
+bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2);
bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2,
BinaryOperator::Opcode Opc);
bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2,
BinaryOperator::Opcode Opc);
+const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State,
+ const MemRegion *Reg);
+SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr,
+ SymbolRef OldSym, SymbolRef NewSym);
bool isIteratorType(const QualType &Type) {
if (Type->isPointerType())
@@ -943,6 +1683,11 @@ bool isIterator(const CXXRecordDecl *CRD) {
HasPostIncrOp && HasDerefOp;
}
+bool isComparisonOperator(OverloadedOperatorKind OK) {
+ return OK == OO_EqualEqual || OK == OO_ExclaimEqual || OK == OO_Less ||
+ OK == OO_LessEqual || OK == OO_Greater || OK == OO_GreaterEqual;
+}
+
bool isBeginCall(const FunctionDecl *Func) {
const auto *IdInfo = Func->getIdentifier();
if (!IdInfo)
@@ -957,10 +1702,139 @@ bool isEndCall(const FunctionDecl *Func) {
return IdInfo->getName().endswith_lower("end");
}
+bool isAssignCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() > 2)
+ return false;
+ return IdInfo->getName() == "assign";
+}
+
+bool isClearCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() > 0)
+ return false;
+ return IdInfo->getName() == "clear";
+}
+
+bool isPushBackCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() != 1)
+ return false;
+ return IdInfo->getName() == "push_back";
+}
+
+bool isEmplaceBackCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 1)
+ return false;
+ return IdInfo->getName() == "emplace_back";
+}
+
+bool isPopBackCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() > 0)
+ return false;
+ return IdInfo->getName() == "pop_back";
+}
+
+bool isPushFrontCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() != 1)
+ return false;
+ return IdInfo->getName() == "push_front";
+}
+
+bool isEmplaceFrontCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 1)
+ return false;
+ return IdInfo->getName() == "emplace_front";
+}
+
+bool isPopFrontCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() > 0)
+ return false;
+ return IdInfo->getName() == "pop_front";
+}
+
+bool isInsertCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 2 || Func->getNumParams() > 3)
+ return false;
+ if (!isIteratorType(Func->getParamDecl(0)->getType()))
+ return false;
+ return IdInfo->getName() == "insert";
+}
+
+bool isEmplaceCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 2)
+ return false;
+ if (!isIteratorType(Func->getParamDecl(0)->getType()))
+ return false;
+ return IdInfo->getName() == "emplace";
+}
+
+bool isEraseCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 1 || Func->getNumParams() > 2)
+ return false;
+ if (!isIteratorType(Func->getParamDecl(0)->getType()))
+ return false;
+ if (Func->getNumParams() == 2 &&
+ !isIteratorType(Func->getParamDecl(1)->getType()))
+ return false;
+ return IdInfo->getName() == "erase";
+}
+
+bool isEraseAfterCall(const FunctionDecl *Func) {
+ const auto *IdInfo = Func->getIdentifier();
+ if (!IdInfo)
+ return false;
+ if (Func->getNumParams() < 1 || Func->getNumParams() > 2)
+ return false;
+ if (!isIteratorType(Func->getParamDecl(0)->getType()))
+ return false;
+ if (Func->getNumParams() == 2 &&
+ !isIteratorType(Func->getParamDecl(1)->getType()))
+ return false;
+ return IdInfo->getName() == "erase_after";
+}
+
+bool isAssignmentOperator(OverloadedOperatorKind OK) { return OK == OO_Equal; }
+
bool isSimpleComparisonOperator(OverloadedOperatorKind OK) {
return OK == OO_EqualEqual || OK == OO_ExclaimEqual;
}
+bool isAccessOperator(OverloadedOperatorKind OK) {
+ return isDereferenceOperator(OK) || isIncrementOperator(OK) ||
+ isDecrementOperator(OK) || isRandomIncrOrDecrOperator(OK);
+}
+
bool isDereferenceOperator(OverloadedOperatorKind OK) {
return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar ||
OK == OO_Subscript;
@@ -996,6 +1870,66 @@ BinaryOperator::Opcode getOpcode(const SymExpr *SE) {
return BO_Comma; // Extremal value, neither EQ nor NE
}
+bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg) {
+ const auto *CRD = getCXXRecordDecl(State, Reg);
+ if (!CRD)
+ return false;
+
+ for (const auto *Method : CRD->methods()) {
+ if (!Method->isOverloadedOperator())
+ continue;
+ const auto OPK = Method->getOverloadedOperator();
+ if (OPK == OO_Subscript) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool frontModifiable(ProgramStateRef State, const MemRegion *Reg) {
+ const auto *CRD = getCXXRecordDecl(State, Reg);
+ if (!CRD)
+ return false;
+
+ for (const auto *Method : CRD->methods()) {
+ if (!Method->getDeclName().isIdentifier())
+ continue;
+ if (Method->getName() == "push_front" || Method->getName() == "pop_front") {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool backModifiable(ProgramStateRef State, const MemRegion *Reg) {
+ const auto *CRD = getCXXRecordDecl(State, Reg);
+ if (!CRD)
+ return false;
+
+ for (const auto *Method : CRD->methods()) {
+ if (!Method->getDeclName().isIdentifier())
+ continue;
+ if (Method->getName() == "push_back" || Method->getName() == "pop_back") {
+ return true;
+ }
+ }
+ return false;
+}
+
+const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State,
+ const MemRegion *Reg) {
+ auto TI = getDynamicTypeInfo(State, Reg);
+ if (!TI.isValid())
+ return nullptr;
+
+ auto Type = TI.getType();
+ if (const auto *RefT = Type->getAs<ReferenceType>()) {
+ Type = RefT->getPointeeType();
+ }
+
+ return Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl();
+}
+
const RegionOrSymbol getRegionOrSymbol(const SVal &Val) {
if (const auto Reg = Val.getAsRegion()) {
return Reg;
@@ -1097,7 +2031,8 @@ ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont,
const IteratorPosition *getIteratorPosition(ProgramStateRef State,
const SVal &Val) {
- if (const auto Reg = Val.getAsRegion()) {
+ if (auto Reg = Val.getAsRegion()) {
+ Reg = Reg->getMostDerivedObjectRegion();
return State->get<IteratorRegionMap>(Reg);
} else if (const auto Sym = Val.getAsSymbol()) {
return State->get<IteratorSymbolMap>(Sym);
@@ -1110,7 +2045,8 @@ const IteratorPosition *getIteratorPosition(ProgramStateRef State,
const IteratorPosition *getIteratorPosition(ProgramStateRef State,
RegionOrSymbol RegOrSym) {
if (RegOrSym.is<const MemRegion *>()) {
- return State->get<IteratorRegionMap>(RegOrSym.get<const MemRegion *>());
+ auto Reg = RegOrSym.get<const MemRegion *>()->getMostDerivedObjectRegion();
+ return State->get<IteratorRegionMap>(Reg);
} else if (RegOrSym.is<SymbolRef>()) {
return State->get<IteratorSymbolMap>(RegOrSym.get<SymbolRef>());
}
@@ -1119,7 +2055,8 @@ const IteratorPosition *getIteratorPosition(ProgramStateRef State,
ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val,
const IteratorPosition &Pos) {
- if (const auto Reg = Val.getAsRegion()) {
+ if (auto Reg = Val.getAsRegion()) {
+ Reg = Reg->getMostDerivedObjectRegion();
return State->set<IteratorRegionMap>(Reg, Pos);
} else if (const auto Sym = Val.getAsSymbol()) {
return State->set<IteratorSymbolMap>(Sym, Pos);
@@ -1133,8 +2070,8 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State,
RegionOrSymbol RegOrSym,
const IteratorPosition &Pos) {
if (RegOrSym.is<const MemRegion *>()) {
- return State->set<IteratorRegionMap>(RegOrSym.get<const MemRegion *>(),
- Pos);
+ auto Reg = RegOrSym.get<const MemRegion *>()->getMostDerivedObjectRegion();
+ return State->set<IteratorRegionMap>(Reg, Pos);
} else if (RegOrSym.is<SymbolRef>()) {
return State->set<IteratorSymbolMap>(RegOrSym.get<SymbolRef>(), Pos);
}
@@ -1142,7 +2079,8 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State,
}
ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) {
- if (const auto Reg = Val.getAsRegion()) {
+ if (auto Reg = Val.getAsRegion()) {
+ Reg = Reg->getMostDerivedObjectRegion();
return State->remove<IteratorRegionMap>(Reg);
} else if (const auto Sym = Val.getAsSymbol()) {
return State->remove<IteratorSymbolMap>(Sym);
@@ -1211,6 +2149,164 @@ bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) {
return false;
}
+bool isBoundThroughLazyCompoundVal(const Environment &Env,
+ const MemRegion *Reg) {
+ for (const auto Binding: Env) {
+ if (const auto LCVal = Binding.second.getAs<nonloc::LazyCompoundVal>()) {
+ if (LCVal->getRegion() == Reg)
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template <typename Condition, typename Process>
+ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond,
+ Process Proc) {
+ auto &RegionMapFactory = State->get_context<IteratorRegionMap>();
+ auto RegionMap = State->get<IteratorRegionMap>();
+ bool Changed = false;
+ for (const auto Reg : RegionMap) {
+ if (Cond(Reg.second)) {
+ RegionMap = RegionMapFactory.add(RegionMap, Reg.first, Proc(Reg.second));
+ Changed = true;
+ }
+ }
+
+ if (Changed)
+ State = State->set<IteratorRegionMap>(RegionMap);
+
+ auto &SymbolMapFactory = State->get_context<IteratorSymbolMap>();
+ auto SymbolMap = State->get<IteratorSymbolMap>();
+ Changed = false;
+ for (const auto Sym : SymbolMap) {
+ if (Cond(Sym.second)) {
+ SymbolMap = SymbolMapFactory.add(SymbolMap, Sym.first, Proc(Sym.second));
+ Changed = true;
+ }
+ }
+
+ if (Changed)
+ State = State->set<IteratorSymbolMap>(SymbolMap);
+
+ return State;
+}
+
+ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State,
+ const MemRegion *Cont) {
+ auto MatchCont = [&](const IteratorPosition &Pos) {
+ return Pos.getContainer() == Cont;
+ };
+ auto Invalidate = [&](const IteratorPosition &Pos) {
+ return Pos.invalidate();
+ };
+ return processIteratorPositions(State, MatchCont, Invalidate);
+}
+
+ProgramStateRef
+invalidateAllIteratorPositionsExcept(ProgramStateRef State,
+ const MemRegion *Cont, SymbolRef Offset,
+ BinaryOperator::Opcode Opc) {
+ auto MatchContAndCompare = [&](const IteratorPosition &Pos) {
+ return Pos.getContainer() == Cont &&
+ !compare(State, Pos.getOffset(), Offset, Opc);
+ };
+ auto Invalidate = [&](const IteratorPosition &Pos) {
+ return Pos.invalidate();
+ };
+ return processIteratorPositions(State, MatchContAndCompare, Invalidate);
+}
+
+ProgramStateRef invalidateIteratorPositions(ProgramStateRef State,
+ SymbolRef Offset,
+ BinaryOperator::Opcode Opc) {
+ auto Compare = [&](const IteratorPosition &Pos) {
+ return compare(State, Pos.getOffset(), Offset, Opc);
+ };
+ auto Invalidate = [&](const IteratorPosition &Pos) {
+ return Pos.invalidate();
+ };
+ return processIteratorPositions(State, Compare, Invalidate);
+}
+
+ProgramStateRef invalidateIteratorPositions(ProgramStateRef State,
+ SymbolRef Offset1,
+ BinaryOperator::Opcode Opc1,
+ SymbolRef Offset2,
+ BinaryOperator::Opcode Opc2) {
+ auto Compare = [&](const IteratorPosition &Pos) {
+ return compare(State, Pos.getOffset(), Offset1, Opc1) &&
+ compare(State, Pos.getOffset(), Offset2, Opc2);
+ };
+ auto Invalidate = [&](const IteratorPosition &Pos) {
+ return Pos.invalidate();
+ };
+ return processIteratorPositions(State, Compare, Invalidate);
+}
+
+ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State,
+ const MemRegion *Cont,
+ const MemRegion *NewCont) {
+ auto MatchCont = [&](const IteratorPosition &Pos) {
+ return Pos.getContainer() == Cont;
+ };
+ auto ReAssign = [&](const IteratorPosition &Pos) {
+ return Pos.reAssign(NewCont);
+ };
+ return processIteratorPositions(State, MatchCont, ReAssign);
+}
+
+ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State,
+ const MemRegion *Cont,
+ const MemRegion *NewCont,
+ SymbolRef Offset,
+ BinaryOperator::Opcode Opc) {
+ auto MatchContAndCompare = [&](const IteratorPosition &Pos) {
+ return Pos.getContainer() == Cont &&
+ !compare(State, Pos.getOffset(), Offset, Opc);
+ };
+ auto ReAssign = [&](const IteratorPosition &Pos) {
+ return Pos.reAssign(NewCont);
+ };
+ return processIteratorPositions(State, MatchContAndCompare, ReAssign);
+}
+
+// This function rebases symbolic expression `OldSym + Int` to `NewSym + Int`,
+// `OldSym - Int` to `NewSym - Int` and `OldSym` to `NewSym` in any iterator
+// position offsets where `CondSym` is true.
+ProgramStateRef rebaseSymbolInIteratorPositionsIf(
+ ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym,
+ SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc) {
+ auto LessThanEnd = [&](const IteratorPosition &Pos) {
+ return compare(State, Pos.getOffset(), CondSym, Opc);
+ };
+ auto RebaseSymbol = [&](const IteratorPosition &Pos) {
+ return Pos.setTo(rebaseSymbol(State, SVB, Pos.getOffset(), OldSym,
+ NewSym));
+ };
+ return processIteratorPositions(State, LessThanEnd, RebaseSymbol);
+}
+
+// This function rebases symbolic expression `OldExpr + Int` to `NewExpr + Int`,
+// `OldExpr - Int` to `NewExpr - Int` and `OldExpr` to `NewExpr` in expression
+// `OrigExpr`.
+SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB,
+ SymbolRef OrigExpr, SymbolRef OldExpr,
+ SymbolRef NewSym) {
+ auto &SymMgr = SVB.getSymbolManager();
+ auto Diff = SVB.evalBinOpNN(State, BO_Sub, nonloc::SymbolVal(OrigExpr),
+ nonloc::SymbolVal(OldExpr),
+ SymMgr.getType(OrigExpr));
+
+ const auto DiffInt = Diff.getAs<nonloc::ConcreteInt>();
+ if (!DiffInt)
+ return OrigExpr;
+
+ return SVB.evalBinOpNN(State, BO_Add, *DiffInt, nonloc::SymbolVal(NewSym),
+ SymMgr.getType(OrigExpr)).getAsSymbol();
+}
+
bool isZero(ProgramStateRef State, const NonLoc &Val) {
auto &BVF = State->getBasicVals();
return compare(State, Val,
@@ -1218,14 +2314,27 @@ bool isZero(ProgramStateRef State, const NonLoc &Val) {
BO_EQ);
}
-bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) {
+bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) {
const auto *Cont = Pos.getContainer();
const auto *CData = getContainerData(State, Cont);
if (!CData)
return false;
- // Out of range means less than the begin symbol or greater or equal to the
- // end symbol.
+ const auto End = CData->getEnd();
+ if (End) {
+ if (isEqual(State, Pos.getOffset(), End)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos) {
+ const auto *Cont = Pos.getContainer();
+ const auto *CData = getContainerData(State, Cont);
+ if (!CData)
+ return false;
const auto Beg = CData->getBegin();
if (Beg) {
@@ -1234,9 +2343,18 @@ bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) {
}
}
+ return false;
+}
+
+bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) {
+ const auto *Cont = Pos.getContainer();
+ const auto *CData = getContainerData(State, Cont);
+ if (!CData)
+ return false;
+
const auto End = CData->getEnd();
if (End) {
- if (isGreaterOrEqual(State, Pos.getOffset(), End)) {
+ if (isGreater(State, Pos.getOffset(), End)) {
return true;
}
}
@@ -1248,8 +2366,12 @@ bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) {
return compare(State, Sym1, Sym2, BO_LT);
}
-bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) {
- return compare(State, Sym1, Sym2, BO_GE);
+bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) {
+ return compare(State, Sym1, Sym2, BO_GT);
+}
+
+bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) {
+ return compare(State, Sym1, Sym2, BO_EQ);
}
bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2,
@@ -1257,6 +2379,7 @@ bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2,
return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc);
}
+
bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2,
BinaryOperator::Opcode Opc) {
auto &SVB = State->getStateManager().getSValBuilder();
@@ -1281,4 +2404,5 @@ bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2,
}
REGISTER_CHECKER(IteratorRangeChecker)
-
+REGISTER_CHECKER(MismatchedIteratorChecker)
+REGISTER_CHECKER(InvalidatedIteratorChecker)
diff --git a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
index 2fb627184eb9..aade62fd7491 100644
--- a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp
@@ -28,7 +28,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/StmtVisitor.h"
diff --git a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
index db4fbca36deb..df238f2b2e45 100644
--- a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
@@ -32,8 +32,7 @@ static bool IsLLVMStringRef(QualType T) {
if (!RT)
return false;
- return StringRef(QualType(RT, 0).getAsString()) ==
- "class StringRef";
+ return StringRef(QualType(RT, 0).getAsString()) == "class StringRef";
}
/// Check whether the declaration is semantically inside the top-level
diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
index 849b1193c042..eda39efeca17 100644
--- a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
@@ -125,7 +125,6 @@ public:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -1003,7 +1002,6 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL,
std::shared_ptr<PathDiagnosticPiece>
NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC, BugReport &BR) {
if (Satisfied)
return nullptr;
@@ -1400,7 +1398,8 @@ void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) {
NonLocalizedStringChecker *checker =
mgr.registerChecker<NonLocalizedStringChecker>();
checker->IsAggressive =
- mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false);
+ mgr.getAnalyzerOptions().getCheckerBooleanOption("AggressiveReport",
+ false, checker);
}
void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) {
diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp
index e9ec7a0c4365..fb9bccebe465 100644
--- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp
+++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp
@@ -87,7 +87,6 @@ void MPIBugReporter::reportUnmatchedWait(
std::shared_ptr<PathDiagnosticPiece>
MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) {
@@ -96,13 +95,13 @@ MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N,
const Request *const Req = N->getState()->get<RequestMap>(RequestRegion);
const Request *const PrevReq =
- PrevN->getState()->get<RequestMap>(RequestRegion);
+ N->getFirstPred()->getState()->get<RequestMap>(RequestRegion);
// Check if request was previously unused or in a different state.
if ((Req && !PrevReq) || (Req->CurrentState != PrevReq->CurrentState)) {
IsNodeFound = true;
- ProgramPoint P = PrevN->getLocation();
+ ProgramPoint P = N->getFirstPred()->getLocation();
PathDiagnosticLocation L =
PathDiagnosticLocation::create(P, BRC.getSourceManager());
diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h
index 40eb0631d7c5..32fcb07e3371 100644
--- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h
+++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h
@@ -91,7 +91,6 @@ private:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp
index 696cf39473d5..28c6898f7947 100644
--- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp
@@ -16,7 +16,7 @@
//===----------------------------------------------------------------------===//
#include "MPIChecker.h"
-#include "../ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
namespace clang {
namespace ento {
@@ -100,9 +100,6 @@ void MPIChecker::checkUnmatchedWaits(const CallEvent &PreCallEvent,
void MPIChecker::checkMissingWaits(SymbolReaper &SymReaper,
CheckerContext &Ctx) const {
- if (!SymReaper.hasDeadSymbols())
- return;
-
ProgramStateRef State = Ctx.getState();
const auto &Requests = State->get<RequestMap>();
if (Requests.isEmpty())
diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
index b8ef6701c0df..06e27fc5718d 100644
--- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp
@@ -12,10 +12,11 @@
// to be freed using a call to SecKeychainItemFreeContent.
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
@@ -29,6 +30,7 @@ namespace {
class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>,
check::PostStmt<CallExpr>,
check::DeadSymbols,
+ check::PointerEscape,
eval::Assume> {
mutable std::unique_ptr<BugType> BT;
@@ -58,6 +60,10 @@ public:
void checkPreStmt(const CallExpr *S, CheckerContext &C) const;
void checkPostStmt(const CallExpr *S, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+ ProgramStateRef checkPointerEscape(ProgramStateRef State,
+ const InvalidatedSymbols &Escaped,
+ const CallEvent *Call,
+ PointerEscapeKind Kind) const;
ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
bool Assumption) const;
void printState(raw_ostream &Out, ProgramStateRef State,
@@ -135,7 +141,6 @@ private:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
};
@@ -571,14 +576,52 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR,
C.addTransition(State, N);
}
+ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape(
+ ProgramStateRef State, const InvalidatedSymbols &Escaped,
+ const CallEvent *Call, PointerEscapeKind Kind) const {
+ // FIXME: This branch doesn't make any sense at all, but it is an overfitted
+ // replacement for a previous overfitted code that was making even less sense.
+ if (!Call || Call->getDecl())
+ return State;
+
+ for (auto I : State->get<AllocatedData>()) {
+ SymbolRef Sym = I.first;
+ if (Escaped.count(Sym))
+ State = State->remove<AllocatedData>(Sym);
+
+ // This checker is special. Most checkers in fact only track symbols of
+ // SymbolConjured type, eg. symbols returned from functions such as
+ // malloc(). This checker tracks symbols returned as out-parameters.
+ //
+ // When a function is evaluated conservatively, the out-parameter's pointee
+ // base region gets invalidated with a SymbolConjured. If the base region is
+ // larger than the region we're interested in, the value we're interested in
+ // would be SymbolDerived based on that SymbolConjured. However, such
+ // SymbolDerived will never be listed in the Escaped set when the base
+ // region is invalidated because ExprEngine doesn't know which symbols
+ // were derived from a given symbol, while there can be infinitely many
+ // valid symbols derived from any given symbol.
+ //
+ // Hence the extra boilerplate: remove the derived symbol when its parent
+ // symbol escapes.
+ //
+ if (const auto *SD = dyn_cast<SymbolDerived>(Sym)) {
+ SymbolRef ParentSym = SD->getParentSymbol();
+ if (Escaped.count(ParentSym))
+ State = State->remove<AllocatedData>(Sym);
+ }
+ }
+ return State;
+}
+
std::shared_ptr<PathDiagnosticPiece>
MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode(
- const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
- BugReport &BR) {
+ const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) {
const AllocationState *AS = N->getState()->get<AllocatedData>(Sym);
if (!AS)
return nullptr;
- const AllocationState *ASPrev = PrevN->getState()->get<AllocatedData>(Sym);
+ const AllocationState *ASPrev =
+ N->getFirstPred()->getState()->get<AllocatedData>(Sym);
if (ASPrev)
return nullptr;
diff --git a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
index 437378e53daa..f5976d7da4c1 100644
--- a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -83,7 +83,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE,
// that dispatch_once is a macro that wraps a call to _dispatch_once.
// _dispatch_once is then a function which then calls the real dispatch_once.
// Users do not care; they just want the warning at the top-level call.
- if (CE->getLocStart().isMacroID()) {
+ if (CE->getBeginLoc().isMacroID()) {
StringRef TrimmedFName = FName.ltrim('_');
if (TrimmedFName != FName)
FName = TrimmedFName;
diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
index ebaf79a780c0..ae1b1fc837be 100644
--- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "InterCheckerAPI.h"
#include "clang/AST/Attr.h"
#include "clang/AST/ParentMap.h"
@@ -161,6 +161,7 @@ class MallocChecker : public Checker<check::DeadSymbols,
check::PointerEscape,
check::ConstPointerEscape,
check::PreStmt<ReturnStmt>,
+ check::EndFunction,
check::PreCall,
check::PostStmt<CallExpr>,
check::PostStmt<CXXNewExpr>,
@@ -193,6 +194,7 @@ public:
CK_NewDeleteChecker,
CK_NewDeleteLeaksChecker,
CK_MismatchedDeallocatorChecker,
+ CK_InnerPointerChecker,
CK_NumCheckKinds
};
@@ -217,6 +219,7 @@ public:
void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
+ void checkEndFunction(const ReturnStmt *S, CheckerContext &C) const;
ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
bool Assumption) const;
void checkLocation(SVal l, bool isLoad, const Stmt *S,
@@ -353,7 +356,7 @@ private:
static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE,
ProgramStateRef State);
- ///Check if the memory associated with this symbol was released.
+ /// Check if the memory associated with this symbol was released.
bool isReleased(SymbolRef Sym, CheckerContext &C) const;
bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const;
@@ -377,13 +380,16 @@ private:
ProgramStateRef State,
SymbolRef &EscapingSymbol) const;
- // Implementation of the checkPointerEscape callabcks.
+ // Implementation of the checkPointerEscape callbacks.
ProgramStateRef checkPointerEscapeAux(ProgramStateRef State,
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind,
bool(*CheckRefState)(const RefState*)) const;
+ // Implementation of the checkPreStmt and checkEndFunction callbacks.
+ void checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const;
+
///@{
/// Tells if a given family/call/symbol is tracked by the current checker.
/// Sets CheckKind to the kind of the checker responsible for this
@@ -511,7 +517,6 @@ private:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -707,10 +712,8 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD,
return false;
}
-// Tells if the callee is one of the following:
-// 1) A global non-placement new/delete operator function.
-// 2) A global placement operator function with the single placement argument
-// of type std::nothrow_t.
+// Tells if the callee is one of the builtin new/delete operators, including
+// placement operators and other standard overloads.
bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD,
ASTContext &C) const {
if (!FD)
@@ -721,23 +724,11 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD,
Kind != OO_Delete && Kind != OO_Array_Delete)
return false;
- // Skip all operator new/delete methods.
- if (isa<CXXMethodDecl>(FD))
- return false;
-
- // Return true if tested operator is a standard placement nothrow operator.
- if (FD->getNumParams() == 2) {
- QualType T = FD->getParamDecl(1)->getType();
- if (const IdentifierInfo *II = T.getBaseTypeIdentifier())
- return II->getName().equals("nothrow_t");
- }
-
- // Skip placement operators.
- if (FD->getNumParams() != 1 || FD->isVariadic())
- return false;
-
- // One of the standard new/new[]/delete/delete[] non-placement operators.
- return true;
+ // This is standard if and only if it's not defined in a user file.
+ SourceLocation L = FD->getLocation();
+ // If the header for operator delete is not included, it's still defined
+ // in an invalid source location. Check to make sure we don't crash.
+ return !L.isValid() || C.getSourceManager().isInSystemHeader(L);
}
llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc(
@@ -1082,12 +1073,6 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) {
void MallocChecker::processNewAllocation(const CXXNewExpr *NE,
CheckerContext &C,
SVal Target) const {
- if (NE->getNumPlacementArgs())
- for (CXXNewExpr::const_arg_iterator I = NE->placement_arg_begin(),
- E = NE->placement_arg_end(); I != E; ++I)
- if (SymbolRef Sym = C.getSVal(*I).getAsSymbol())
- checkUseAfterFree(Sym, C, *I);
-
if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext()))
return;
@@ -1098,7 +1083,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE,
ProgramStateRef State = C.getState();
// The return value from operator new is bound to a specified initialization
// value (if any) and we don't want to loose this value. So we call
- // MallocUpdateRefState() instead of MallocMemAux() which breakes the
+ // MallocUpdateRefState() instead of MallocMemAux() which breaks the
// existing binding.
State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray
: AF_CXXNew, Target);
@@ -1109,7 +1094,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE,
void MallocChecker::checkPostStmt(const CXXNewExpr *NE,
CheckerContext &C) const {
- if (!C.getAnalysisManager().getAnalyzerOptions().mayInlineCXXAllocator())
+ if (!C.getAnalysisManager().getAnalyzerOptions().MayInlineCXXAllocator)
processNewAllocation(NE, C, C.getSVal(NE));
}
@@ -1657,13 +1642,10 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
case AF_IfNameIndex: {
if (ChecksEnabled[CK_MallocChecker])
return CK_MallocChecker;
-
- return Optional<MallocChecker::CheckKind>();
+ return None;
}
case AF_CXXNew:
- case AF_CXXNewArray:
- // FIXME: Add new CheckKind for AF_InnerBuffer.
- case AF_InnerBuffer: {
+ case AF_CXXNewArray: {
if (IsALeakCheck) {
if (ChecksEnabled[CK_NewDeleteLeaksChecker])
return CK_NewDeleteLeaksChecker;
@@ -1672,7 +1654,12 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family,
if (ChecksEnabled[CK_NewDeleteChecker])
return CK_NewDeleteChecker;
}
- return Optional<MallocChecker::CheckKind>();
+ return None;
+ }
+ case AF_InnerBuffer: {
+ if (ChecksEnabled[CK_InnerPointerChecker])
+ return CK_InnerPointerChecker;
+ return None;
}
case AF_None: {
llvm_unreachable("no family");
@@ -1975,7 +1962,8 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range,
SymbolRef Sym) const {
if (!ChecksEnabled[CK_MallocChecker] &&
- !ChecksEnabled[CK_NewDeleteChecker])
+ !ChecksEnabled[CK_NewDeleteChecker] &&
+ !ChecksEnabled[CK_InnerPointerChecker])
return;
Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym);
@@ -1987,15 +1975,20 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range,
BT_UseFree[*CheckKind].reset(new BugType(
CheckNames[*CheckKind], "Use-after-free", categories::MemoryError));
+ AllocationFamily AF =
+ C.getState()->get<RegionState>(Sym)->getAllocationFamily();
+
auto R = llvm::make_unique<BugReport>(*BT_UseFree[*CheckKind],
- "Use of memory after it is freed", N);
+ AF == AF_InnerBuffer
+ ? "Inner pointer of container used after re/deallocation"
+ : "Use of memory after it is freed",
+ N);
R->markInteresting(Sym);
R->addRange(Range);
R->addVisitor(llvm::make_unique<MallocBugVisitor>(Sym));
- const RefState *RS = C.getState()->get<RegionState>(Sym);
- if (RS->getAllocationFamily() == AF_InnerBuffer)
+ if (AF == AF_InnerBuffer)
R->addVisitor(allocation_state::getInnerPointerBRVisitor(Sym));
C.emitReport(std::move(R));
@@ -2352,13 +2345,11 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N,
void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const
{
- if (!SymReaper.hasDeadSymbols())
- return;
-
ProgramStateRef state = C.getState();
- RegionStateTy RS = state->get<RegionState>();
+ RegionStateTy OldRS = state->get<RegionState>();
RegionStateTy::Factory &F = state->get_context<RegionState>();
+ RegionStateTy RS = OldRS;
SmallVector<SymbolRef, 2> Errors;
for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) {
if (SymReaper.isDead(I->first)) {
@@ -2366,10 +2357,18 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper,
Errors.push_back(I->first);
// Remove the dead symbol from the map.
RS = F.remove(RS, I->first);
-
}
}
+ if (RS == OldRS) {
+ // We shouldn't have touched other maps yet.
+ assert(state->get<ReallocPairs>() ==
+ C.getState()->get<ReallocPairs>());
+ assert(state->get<FreeReturnValue>() ==
+ C.getState()->get<FreeReturnValue>());
+ return;
+ }
+
// Cleanup the Realloc Pairs Map.
ReallocPairsTy RP = state->get<ReallocPairs>();
for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) {
@@ -2425,10 +2424,6 @@ void MallocChecker::checkPreCall(const CallEvent &Call,
isCMemFunction(FD, Ctx, AF_IfNameIndex,
MemoryOperationKind::MOK_Free)))
return;
-
- if (ChecksEnabled[CK_NewDeleteChecker] &&
- isStandardNewDelete(FD, Ctx))
- return;
}
// Check if the callee of a method is deleted.
@@ -2451,7 +2446,24 @@ void MallocChecker::checkPreCall(const CallEvent &Call,
}
}
-void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const {
+void MallocChecker::checkPreStmt(const ReturnStmt *S,
+ CheckerContext &C) const {
+ checkEscapeOnReturn(S, C);
+}
+
+// In the CFG, automatic destructors come after the return statement.
+// This callback checks for returning memory that is freed by automatic
+// destructors, as those cannot be reached in checkPreStmt().
+void MallocChecker::checkEndFunction(const ReturnStmt *S,
+ CheckerContext &C) const {
+ checkEscapeOnReturn(S, C);
+}
+
+void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S,
+ CheckerContext &C) const {
+ if (!S)
+ return;
+
const Expr *E = S->getRetValue();
if (!E)
return;
@@ -2509,8 +2521,7 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE,
}
state =
- state->scanReachableSymbols<StopTrackingCallback>(Regions.data(),
- Regions.data() + Regions.size()).getState();
+ state->scanReachableSymbols<StopTrackingCallback>(Regions).getState();
C.addTransition(state);
}
@@ -2858,11 +2869,10 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) {
}
std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
- const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
- BugReport &BR) {
+ const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) {
ProgramStateRef state = N->getState();
- ProgramStateRef statePrev = PrevN->getState();
+ ProgramStateRef statePrev = N->getFirstPred()->getState();
const RefState *RS = state->get<RegionState>(Sym);
const RefState *RSPrev = statePrev->get<RegionState>(Sym);
@@ -2918,13 +2928,22 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
case AF_CXXNewArray:
case AF_IfNameIndex:
Msg = "Memory is released";
+ StackHint = new StackHintGeneratorForSymbol(Sym,
+ "Returning; memory was released");
break;
case AF_InnerBuffer: {
- OS << "Inner pointer invalidated by call to ";
+ const MemRegion *ObjRegion =
+ allocation_state::getContainerObjRegion(statePrev, Sym);
+ const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion);
+ QualType ObjTy = TypedRegion->getValueType();
+ OS << "Inner buffer of '" << ObjTy.getAsString() << "' ";
+
if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) {
- OS << "destructor";
+ OS << "deallocated by call to destructor";
+ StackHint = new StackHintGeneratorForSymbol(Sym,
+ "Returning; inner buffer was deallocated");
} else {
- OS << "'";
+ OS << "reallocated by call to '";
const Stmt *S = RS->getStmt();
if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) {
OS << MemCallE->getMethodDecl()->getNameAsString();
@@ -2937,6 +2956,8 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
OS << (D ? D->getNameAsString() : "unknown");
}
OS << "'";
+ StackHint = new StackHintGeneratorForSymbol(Sym,
+ "Returning; inner buffer was reallocated");
}
Msg = OS.str();
break;
@@ -2944,8 +2965,6 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode(
case AF_None:
llvm_unreachable("Unhandled allocation family!");
}
- StackHint = new StackHintGeneratorForSymbol(Sym,
- "Returning; memory was released");
// See if we're releasing memory while inlining a destructor
// (or one of its callees). This turns on various common
@@ -3071,7 +3090,7 @@ markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) {
void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) {
registerCStringCheckerBasic(mgr);
MallocChecker *checker = mgr.registerChecker<MallocChecker>();
- checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption(
+ checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption(
"Optimistic", false, checker);
checker->ChecksEnabled[MallocChecker::CK_NewDeleteLeaksChecker] = true;
checker->CheckNames[MallocChecker::CK_NewDeleteLeaksChecker] =
@@ -3087,11 +3106,23 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) {
}
}
+// Intended to be used in InnerPointerChecker to register the part of
+// MallocChecker connected to it.
+void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) {
+ registerCStringCheckerBasic(mgr);
+ MallocChecker *checker = mgr.registerChecker<MallocChecker>();
+ checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption(
+ "Optimistic", false, checker);
+ checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true;
+ checker->CheckNames[MallocChecker::CK_InnerPointerChecker] =
+ mgr.getCurrentCheckName();
+}
+
#define REGISTER_CHECKER(name) \
void ento::register##name(CheckerManager &mgr) { \
registerCStringCheckerBasic(mgr); \
MallocChecker *checker = mgr.registerChecker<MallocChecker>(); \
- checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption( \
+ checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( \
"Optimistic", false, checker); \
checker->ChecksEnabled[MallocChecker::CK_##name] = true; \
checker->CheckNames[MallocChecker::CK_##name] = mgr.getCurrentCheckName(); \
diff --git a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
index fc2ab1d6e3f7..d02ed48bceaa 100644
--- a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp
@@ -18,7 +18,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/EvaluatedExprVisitor.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -135,9 +135,9 @@ private:
bool isIntZeroExpr(const Expr *E) const {
if (!E->getType()->isIntegralOrEnumerationType())
return false;
- llvm::APSInt Result;
+ Expr::EvalResult Result;
if (E->EvaluateAsInt(Result, Context))
- return Result == 0;
+ return Result.Val.getInt() == 0;
return false;
}
@@ -191,8 +191,11 @@ private:
if (const BinaryOperator *BOp = dyn_cast<BinaryOperator>(rhse)) {
if (BOp->getOpcode() == BO_Div) {
const Expr *denom = BOp->getRHS()->IgnoreParenImpCasts();
- if (denom->EvaluateAsInt(denomVal, Context))
+ Expr::EvalResult Result;
+ if (denom->EvaluateAsInt(Result, Context)) {
+ denomVal = Result.Val.getInt();
denomKnown = true;
+ }
const Expr *numerator = BOp->getLHS()->IgnoreParenImpCasts();
if (numerator->isEvaluatable(Context))
numeratorKnown = true;
diff --git a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
index 80a3fbe1a409..bb245d82bc2b 100644
--- a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/AST/TypeLoc.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
diff --git a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp
deleted file mode 100644
index 19c1d077afa1..000000000000
--- a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp
+++ /dev/null
@@ -1,525 +0,0 @@
-// MisusedMovedObjectChecker.cpp - Check use of moved-from objects. - C++ -===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This defines checker which checks for potential misuses of a moved-from
-// object. That means method calls on the object or copying it in moved-from
-// state.
-//
-//===----------------------------------------------------------------------===//
-
-#include "ClangSACheckers.h"
-#include "clang/AST/ExprCXX.h"
-#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
-#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/CheckerManager.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-
-using namespace clang;
-using namespace ento;
-
-namespace {
-
-struct RegionState {
-private:
- enum Kind { Moved, Reported } K;
- RegionState(Kind InK) : K(InK) {}
-
-public:
- bool isReported() const { return K == Reported; }
- bool isMoved() const { return K == Moved; }
-
- static RegionState getReported() { return RegionState(Reported); }
- static RegionState getMoved() { return RegionState(Moved); }
-
- bool operator==(const RegionState &X) const { return K == X.K; }
- void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
-};
-
-class MisusedMovedObjectChecker
- : public Checker<check::PreCall, check::PostCall, check::EndFunction,
- check::DeadSymbols, check::RegionChanges> {
-public:
- void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
- void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
- void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
- void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
- ProgramStateRef
- checkRegionChanges(ProgramStateRef State,
- const InvalidatedSymbols *Invalidated,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions,
- const LocationContext *LCtx, const CallEvent *Call) const;
- void printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const override;
-
-private:
- enum MisuseKind {MK_FunCall, MK_Copy, MK_Move};
- class MovedBugVisitor : public BugReporterVisitor {
- public:
- MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {}
-
- void Profile(llvm::FoldingSetNodeID &ID) const override {
- static int X = 0;
- ID.AddPointer(&X);
- ID.AddPointer(Region);
- }
-
- std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC,
- BugReport &BR) override;
-
- private:
- // The tracked region.
- const MemRegion *Region;
- bool Found;
- };
-
- mutable std::unique_ptr<BugType> BT;
- ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call,
- CheckerContext &C, MisuseKind MK) const;
- bool isInMoveSafeContext(const LocationContext *LC) const;
- bool isStateResetMethod(const CXXMethodDecl *MethodDec) const;
- bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const;
- const ExplodedNode *getMoveLocation(const ExplodedNode *N,
- const MemRegion *Region,
- CheckerContext &C) const;
-};
-} // end anonymous namespace
-
-REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
-
-// If a region is removed all of the subregions needs to be removed too.
-static ProgramStateRef removeFromState(ProgramStateRef State,
- const MemRegion *Region) {
- if (!Region)
- return State;
- for (auto &E : State->get<TrackedRegionMap>()) {
- if (E.first->isSubRegionOf(Region))
- State = State->remove<TrackedRegionMap>(E.first);
- }
- return State;
-}
-
-static bool isAnyBaseRegionReported(ProgramStateRef State,
- const MemRegion *Region) {
- for (auto &E : State->get<TrackedRegionMap>()) {
- if (Region->isSubRegionOf(E.first) && E.second.isReported())
- return true;
- }
- return false;
-}
-
-std::shared_ptr<PathDiagnosticPiece>
-MisusedMovedObjectChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC,
- BugReport &BR) {
- // We need only the last move of the reported object's region.
- // The visitor walks the ExplodedGraph backwards.
- if (Found)
- return nullptr;
- ProgramStateRef State = N->getState();
- ProgramStateRef StatePrev = PrevN->getState();
- const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region);
- const RegionState *TrackedObjectPrev =
- StatePrev->get<TrackedRegionMap>(Region);
- if (!TrackedObject)
- return nullptr;
- if (TrackedObjectPrev && TrackedObject)
- return nullptr;
-
- // Retrieve the associated statement.
- const Stmt *S = PathDiagnosticLocation::getStmt(N);
- if (!S)
- return nullptr;
- Found = true;
-
- std::string ObjectName;
- if (const auto DecReg = Region->getAs<DeclRegion>()) {
- const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl());
- ObjectName = RegionDecl->getNameAsString();
- }
- std::string InfoText;
- if (ObjectName != "")
- InfoText = "'" + ObjectName + "' became 'moved-from' here";
- else
- InfoText = "Became 'moved-from' here";
-
- // Generate the extra diagnostic.
- PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
- N->getLocationContext());
- return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true);
-}
-
-const ExplodedNode *MisusedMovedObjectChecker::getMoveLocation(
- const ExplodedNode *N, const MemRegion *Region, CheckerContext &C) const {
- // Walk the ExplodedGraph backwards and find the first node that referred to
- // the tracked region.
- const ExplodedNode *MoveNode = N;
-
- while (N) {
- ProgramStateRef State = N->getState();
- if (!State->get<TrackedRegionMap>(Region))
- break;
- MoveNode = N;
- N = N->pred_empty() ? nullptr : *(N->pred_begin());
- }
- return MoveNode;
-}
-
-ExplodedNode *MisusedMovedObjectChecker::reportBug(const MemRegion *Region,
- const CallEvent &Call,
- CheckerContext &C,
- MisuseKind MK) const {
- if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
- if (!BT)
- BT.reset(new BugType(this, "Usage of a 'moved-from' object",
- "C++ move semantics"));
-
- // Uniqueing report to the same object.
- PathDiagnosticLocation LocUsedForUniqueing;
- const ExplodedNode *MoveNode = getMoveLocation(N, Region, C);
-
- if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode))
- LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
- MoveStmt, C.getSourceManager(), MoveNode->getLocationContext());
-
- // Creating the error message.
- std::string ErrorMessage;
- switch(MK) {
- case MK_FunCall:
- ErrorMessage = "Method call on a 'moved-from' object";
- break;
- case MK_Copy:
- ErrorMessage = "Copying a 'moved-from' object";
- break;
- case MK_Move:
- ErrorMessage = "Moving a 'moved-from' object";
- break;
- }
- if (const auto DecReg = Region->getAs<DeclRegion>()) {
- const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl());
- ErrorMessage += " '" + RegionDecl->getNameAsString() + "'";
- }
-
- auto R =
- llvm::make_unique<BugReport>(*BT, ErrorMessage, N, LocUsedForUniqueing,
- MoveNode->getLocationContext()->getDecl());
- R->addVisitor(llvm::make_unique<MovedBugVisitor>(Region));
- C.emitReport(std::move(R));
- return N;
- }
- return nullptr;
-}
-
-// Removing the function parameters' MemRegion from the state. This is needed
-// for PODs where the trivial destructor does not even created nor executed.
-void MisusedMovedObjectChecker::checkEndFunction(const ReturnStmt *RS,
- CheckerContext &C) const {
- auto State = C.getState();
- TrackedRegionMapTy Objects = State->get<TrackedRegionMap>();
- if (Objects.isEmpty())
- return;
-
- auto LC = C.getLocationContext();
-
- const auto LD = dyn_cast_or_null<FunctionDecl>(LC->getDecl());
- if (!LD)
- return;
- llvm::SmallSet<const MemRegion *, 8> InvalidRegions;
-
- for (auto Param : LD->parameters()) {
- auto Type = Param->getType().getTypePtrOrNull();
- if (!Type)
- continue;
- if (!Type->isPointerType() && !Type->isReferenceType()) {
- InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion());
- }
- }
-
- if (InvalidRegions.empty())
- return;
-
- for (const auto &E : State->get<TrackedRegionMap>()) {
- if (InvalidRegions.count(E.first->getBaseRegion()))
- State = State->remove<TrackedRegionMap>(E.first);
- }
-
- C.addTransition(State);
-}
-
-void MisusedMovedObjectChecker::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
- const auto *AFC = dyn_cast<AnyFunctionCall>(&Call);
- if (!AFC)
- return;
-
- ProgramStateRef State = C.getState();
- const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl());
- if (!MethodDecl)
- return;
-
- const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl);
-
- const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call);
- // Check if an object became moved-from.
- // Object can become moved from after a call to move assignment operator or
- // move constructor .
- if (ConstructorDecl && !ConstructorDecl->isMoveConstructor())
- return;
-
- if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator())
- return;
-
- const auto ArgRegion = AFC->getArgSVal(0).getAsRegion();
- if (!ArgRegion)
- return;
-
- // Skip moving the object to itself.
- if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion)
- return;
- if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC))
- if (IC->getCXXThisVal().getAsRegion() == ArgRegion)
- return;
-
- const MemRegion *BaseRegion = ArgRegion->getBaseRegion();
- // Skip temp objects because of their short lifetime.
- if (BaseRegion->getAs<CXXTempObjectRegion>() ||
- AFC->getArgExpr(0)->isRValue())
- return;
- // If it has already been reported do not need to modify the state.
-
- if (State->get<TrackedRegionMap>(ArgRegion))
- return;
- // Mark object as moved-from.
- State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved());
- C.addTransition(State);
-}
-
-bool MisusedMovedObjectChecker::isMoveSafeMethod(
- const CXXMethodDecl *MethodDec) const {
- // We abandon the cases where bool/void/void* conversion happens.
- if (const auto *ConversionDec =
- dyn_cast_or_null<CXXConversionDecl>(MethodDec)) {
- const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull();
- if (!Tp)
- return false;
- if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType())
- return true;
- }
- // Function call `empty` can be skipped.
- if (MethodDec && MethodDec->getDeclName().isIdentifier() &&
- (MethodDec->getName().lower() == "empty" ||
- MethodDec->getName().lower() == "isempty"))
- return true;
-
- return false;
-}
-
-bool MisusedMovedObjectChecker::isStateResetMethod(
- const CXXMethodDecl *MethodDec) const {
- if (MethodDec && MethodDec->getDeclName().isIdentifier()) {
- std::string MethodName = MethodDec->getName().lower();
- if (MethodName == "reset" || MethodName == "clear" ||
- MethodName == "destroy")
- return true;
- }
- return false;
-}
-
-// Don't report an error inside a move related operation.
-// We assume that the programmer knows what she does.
-bool MisusedMovedObjectChecker::isInMoveSafeContext(
- const LocationContext *LC) const {
- do {
- const auto *CtxDec = LC->getDecl();
- auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec);
- auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec);
- auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec);
- if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) ||
- (MethodDec && MethodDec->isOverloadedOperator() &&
- MethodDec->getOverloadedOperator() == OO_Equal) ||
- isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec))
- return true;
- } while ((LC = LC->getParent()));
- return false;
-}
-
-void MisusedMovedObjectChecker::checkPreCall(const CallEvent &Call,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- const LocationContext *LC = C.getLocationContext();
- ExplodedNode *N = nullptr;
-
- // Remove the MemRegions from the map on which a ctor/dtor call or assignment
- // happened.
-
- // Checking constructor calls.
- if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
- State = removeFromState(State, CC->getCXXThisVal().getAsRegion());
- auto CtorDec = CC->getDecl();
- // Check for copying a moved-from object and report the bug.
- if (CtorDec && CtorDec->isCopyOrMoveConstructor()) {
- const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion();
- const RegionState *ArgState = State->get<TrackedRegionMap>(ArgRegion);
- if (ArgState && ArgState->isMoved()) {
- if (!isInMoveSafeContext(LC)) {
- if(CtorDec->isMoveConstructor())
- N = reportBug(ArgRegion, Call, C, MK_Move);
- else
- N = reportBug(ArgRegion, Call, C, MK_Copy);
- State = State->set<TrackedRegionMap>(ArgRegion,
- RegionState::getReported());
- }
- }
- }
- C.addTransition(State, N);
- return;
- }
-
- const auto IC = dyn_cast<CXXInstanceCall>(&Call);
- if (!IC)
- return;
- // In case of destructor call we do not track the object anymore.
- const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
- if (!ThisRegion)
- return;
-
- if (dyn_cast_or_null<CXXDestructorDecl>(Call.getDecl())) {
- State = removeFromState(State, ThisRegion);
- C.addTransition(State);
- return;
- }
-
- const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl());
- if (!MethodDecl)
- return;
- // Checking assignment operators.
- bool OperatorEq = MethodDecl->isOverloadedOperator() &&
- MethodDecl->getOverloadedOperator() == OO_Equal;
- // Remove the tracked object for every assignment operator, but report bug
- // only for move or copy assignment's argument.
- if (OperatorEq) {
- State = removeFromState(State, ThisRegion);
- if (MethodDecl->isCopyAssignmentOperator() ||
- MethodDecl->isMoveAssignmentOperator()) {
- const RegionState *ArgState =
- State->get<TrackedRegionMap>(IC->getArgSVal(0).getAsRegion());
- if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) {
- const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion();
- if(MethodDecl->isMoveAssignmentOperator())
- N = reportBug(ArgRegion, Call, C, MK_Move);
- else
- N = reportBug(ArgRegion, Call, C, MK_Copy);
- State =
- State->set<TrackedRegionMap>(ArgRegion, RegionState::getReported());
- }
- }
- C.addTransition(State, N);
- return;
- }
-
- // The remaining part is check only for method call on a moved-from object.
-
- // We want to investigate the whole object, not only sub-object of a parent
- // class in which the encountered method defined.
- while (const CXXBaseObjectRegion *BR =
- dyn_cast<CXXBaseObjectRegion>(ThisRegion))
- ThisRegion = BR->getSuperRegion();
-
- if (isMoveSafeMethod(MethodDecl))
- return;
-
- if (isStateResetMethod(MethodDecl)) {
- State = removeFromState(State, ThisRegion);
- C.addTransition(State);
- return;
- }
-
- // If it is already reported then we don't report the bug again.
- const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion);
- if (!(ThisState && ThisState->isMoved()))
- return;
-
- // Don't report it in case if any base region is already reported
- if (isAnyBaseRegionReported(State, ThisRegion))
- return;
-
- if (isInMoveSafeContext(LC))
- return;
-
- N = reportBug(ThisRegion, Call, C, MK_FunCall);
- State = State->set<TrackedRegionMap>(ThisRegion, RegionState::getReported());
- C.addTransition(State, N);
-}
-
-void MisusedMovedObjectChecker::checkDeadSymbols(SymbolReaper &SymReaper,
- CheckerContext &C) const {
- ProgramStateRef State = C.getState();
- TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
- for (TrackedRegionMapTy::value_type E : TrackedRegions) {
- const MemRegion *Region = E.first;
- bool IsRegDead = !SymReaper.isLiveRegion(Region);
-
- // Remove the dead regions from the region map.
- if (IsRegDead) {
- State = State->remove<TrackedRegionMap>(Region);
- }
- }
- C.addTransition(State);
-}
-
-ProgramStateRef MisusedMovedObjectChecker::checkRegionChanges(
- ProgramStateRef State, const InvalidatedSymbols *Invalidated,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
- const CallEvent *Call) const {
- // In case of an InstanceCall don't remove the ThisRegion from the GDM since
- // it is handled in checkPreCall and checkPostCall.
- const MemRegion *ThisRegion = nullptr;
- if (const auto *IC = dyn_cast_or_null<CXXInstanceCall>(Call)) {
- ThisRegion = IC->getCXXThisVal().getAsRegion();
- }
-
- for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(),
- E = ExplicitRegions.end();
- I != E; ++I) {
- const auto *Region = *I;
- if (ThisRegion != Region) {
- State = removeFromState(State, Region);
- }
- }
-
- return State;
-}
-
-void MisusedMovedObjectChecker::printState(raw_ostream &Out,
- ProgramStateRef State,
- const char *NL,
- const char *Sep) const {
-
- TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
-
- if (!RS.isEmpty()) {
- Out << Sep << "Moved-from objects :" << NL;
- for (auto I: RS) {
- I.first->dumpToStream(Out);
- if (I.second.isMoved())
- Out << ": moved";
- else
- Out << ": moved and reported";
- Out << NL;
- }
- }
-}
-void ento::registerMisusedMovedObjectChecker(CheckerManager &mgr) {
- mgr.registerChecker<MisusedMovedObjectChecker>();
-}
diff --git a/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp
index 5060b0e0a6e0..e3b24f20b0f0 100644
--- a/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -82,7 +82,9 @@ void ento::registerMmapWriteExecChecker(CheckerManager &mgr) {
MmapWriteExecChecker *Mwec =
mgr.registerChecker<MmapWriteExecChecker>();
Mwec->ProtExecOv =
- mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtExec", 0x04, Mwec);
+ mgr.getAnalyzerOptions()
+ .getCheckerIntegerOption("MmapProtExec", 0x04, Mwec);
Mwec->ProtReadOv =
- mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtRead", 0x01, Mwec);
+ mgr.getAnalyzerOptions()
+ .getCheckerIntegerOption("MmapProtRead", 0x01, Mwec);
}
diff --git a/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
new file mode 100644
index 000000000000..6efa2dfbe5b4
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/MoveChecker.cpp
@@ -0,0 +1,740 @@
+// MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines checker which checks for potential misuses of a moved-from
+// object. That means method calls on the object or copying it in moved-from
+// state.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ExprCXX.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "llvm/ADT/StringSet.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+struct RegionState {
+private:
+ enum Kind { Moved, Reported } K;
+ RegionState(Kind InK) : K(InK) {}
+
+public:
+ bool isReported() const { return K == Reported; }
+ bool isMoved() const { return K == Moved; }
+
+ static RegionState getReported() { return RegionState(Reported); }
+ static RegionState getMoved() { return RegionState(Moved); }
+
+ bool operator==(const RegionState &X) const { return K == X.K; }
+ void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); }
+};
+} // end of anonymous namespace
+
+namespace {
+class MoveChecker
+ : public Checker<check::PreCall, check::PostCall,
+ check::DeadSymbols, check::RegionChanges> {
+public:
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+ void checkPreCall(const CallEvent &MC, CheckerContext &C) const;
+ void checkPostCall(const CallEvent &MC, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+ ProgramStateRef
+ checkRegionChanges(ProgramStateRef State,
+ const InvalidatedSymbols *Invalidated,
+ ArrayRef<const MemRegion *> RequestedRegions,
+ ArrayRef<const MemRegion *> InvalidatedRegions,
+ const LocationContext *LCtx, const CallEvent *Call) const;
+ void printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const override;
+
+private:
+ enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference };
+ enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr };
+
+ enum AggressivenessKind { // In any case, don't warn after a reset.
+ AK_Invalid = -1,
+ AK_KnownsOnly = 0, // Warn only about known move-unsafe classes.
+ AK_KnownsAndLocals = 1, // Also warn about all local objects.
+ AK_All = 2, // Warn on any use-after-move.
+ AK_NumKinds = AK_All
+ };
+
+ static bool misuseCausesCrash(MisuseKind MK) {
+ return MK == MK_Dereference;
+ }
+
+ struct ObjectKind {
+ // Is this a local variable or a local rvalue reference?
+ bool IsLocal;
+ // Is this an STL object? If so, of what kind?
+ StdObjectKind StdKind;
+ };
+
+ // STL smart pointers are automatically re-initialized to null when moved
+ // from. So we can't warn on many methods, but we can warn when it is
+ // dereferenced, which is UB even if the resulting lvalue never gets read.
+ const llvm::StringSet<> StdSmartPtrClasses = {
+ "shared_ptr",
+ "unique_ptr",
+ "weak_ptr",
+ };
+
+ // Not all of these are entirely move-safe, but they do provide *some*
+ // guarantees, and it means that somebody is using them after move
+ // in a valid manner.
+ // TODO: We can still try to identify *unsafe* use after move,
+ // like we did with smart pointers.
+ const llvm::StringSet<> StdSafeClasses = {
+ "basic_filebuf",
+ "basic_ios",
+ "future",
+ "optional",
+ "packaged_task"
+ "promise",
+ "shared_future",
+ "shared_lock",
+ "thread",
+ "unique_lock",
+ };
+
+ // Should we bother tracking the state of the object?
+ bool shouldBeTracked(ObjectKind OK) const {
+ // In non-aggressive mode, only warn on use-after-move of local variables
+ // (or local rvalue references) and of STL objects. The former is possible
+ // because local variables (or local rvalue references) are not tempting
+ // their user to re-use the storage. The latter is possible because STL
+ // objects are known to end up in a valid but unspecified state after the
+ // move and their state-reset methods are also known, which allows us to
+ // predict precisely when use-after-move is invalid.
+ // Some STL objects are known to conform to additional contracts after move,
+ // so they are not tracked. However, smart pointers specifically are tracked
+ // because we can perform extra checking over them.
+ // In aggressive mode, warn on any use-after-move because the user has
+ // intentionally asked us to completely eliminate use-after-move
+ // in his code.
+ return (Aggressiveness == AK_All) ||
+ (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
+ OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr;
+ }
+
+ // Some objects only suffer from some kinds of misuses, but we need to track
+ // them anyway because we cannot know in advance what misuse will we find.
+ bool shouldWarnAbout(ObjectKind OK, MisuseKind MK) const {
+ // Additionally, only warn on smart pointers when they are dereferenced (or
+ // local or we are aggressive).
+ return shouldBeTracked(OK) &&
+ ((Aggressiveness == AK_All) ||
+ (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) ||
+ OK.StdKind != SK_SmartPtr || MK == MK_Dereference);
+ }
+
+ // Obtains ObjectKind of an object. Because class declaration cannot always
+ // be easily obtained from the memory region, it is supplied separately.
+ ObjectKind classifyObject(const MemRegion *MR, const CXXRecordDecl *RD) const;
+
+ // Classifies the object and dumps a user-friendly description string to
+ // the stream.
+ void explainObject(llvm::raw_ostream &OS, const MemRegion *MR,
+ const CXXRecordDecl *RD, MisuseKind MK) const;
+
+ bool belongsTo(const CXXRecordDecl *RD, const llvm::StringSet<> &Set) const;
+
+ class MovedBugVisitor : public BugReporterVisitor {
+ public:
+ MovedBugVisitor(const MoveChecker &Chk, const MemRegion *R,
+ const CXXRecordDecl *RD, MisuseKind MK)
+ : Chk(Chk), Region(R), RD(RD), MK(MK), Found(false) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int X = 0;
+ ID.AddPointer(&X);
+ ID.AddPointer(Region);
+ // Don't add RD because it's, in theory, uniquely determined by
+ // the region. In practice though, it's not always possible to obtain
+ // the declaration directly from the region, that's why we store it
+ // in the first place.
+ }
+
+ std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ private:
+ const MoveChecker &Chk;
+ // The tracked region.
+ const MemRegion *Region;
+ // The class of the tracked object.
+ const CXXRecordDecl *RD;
+ // How exactly the object was misused.
+ const MisuseKind MK;
+ bool Found;
+ };
+
+ AggressivenessKind Aggressiveness;
+
+public:
+ void setAggressiveness(StringRef Str) {
+ Aggressiveness =
+ llvm::StringSwitch<AggressivenessKind>(Str)
+ .Case("KnownsOnly", AK_KnownsOnly)
+ .Case("KnownsAndLocals", AK_KnownsAndLocals)
+ .Case("All", AK_All)
+ .Default(AK_KnownsAndLocals); // A sane default.
+ };
+
+private:
+ mutable std::unique_ptr<BugType> BT;
+
+ // Check if the given form of potential misuse of a given object
+ // should be reported. If so, get it reported. The callback from which
+ // this function was called should immediately return after the call
+ // because this function adds one or two transitions.
+ void modelUse(ProgramStateRef State, const MemRegion *Region,
+ const CXXRecordDecl *RD, MisuseKind MK,
+ CheckerContext &C) const;
+
+ // Returns the exploded node against which the report was emitted.
+ // The caller *must* add any further transitions against this node.
+ ExplodedNode *reportBug(const MemRegion *Region, const CXXRecordDecl *RD,
+ CheckerContext &C, MisuseKind MK) const;
+
+ bool isInMoveSafeContext(const LocationContext *LC) const;
+ bool isStateResetMethod(const CXXMethodDecl *MethodDec) const;
+ bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const;
+ const ExplodedNode *getMoveLocation(const ExplodedNode *N,
+ const MemRegion *Region,
+ CheckerContext &C) const;
+};
+} // end anonymous namespace
+
+REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState)
+
+// If a region is removed all of the subregions needs to be removed too.
+static ProgramStateRef removeFromState(ProgramStateRef State,
+ const MemRegion *Region) {
+ if (!Region)
+ return State;
+ for (auto &E : State->get<TrackedRegionMap>()) {
+ if (E.first->isSubRegionOf(Region))
+ State = State->remove<TrackedRegionMap>(E.first);
+ }
+ return State;
+}
+
+static bool isAnyBaseRegionReported(ProgramStateRef State,
+ const MemRegion *Region) {
+ for (auto &E : State->get<TrackedRegionMap>()) {
+ if (Region->isSubRegionOf(E.first) && E.second.isReported())
+ return true;
+ }
+ return false;
+}
+
+static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) {
+ if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(MR)) {
+ SymbolRef Sym = SR->getSymbol();
+ if (Sym->getType()->isRValueReferenceType())
+ if (const MemRegion *OriginMR = Sym->getOriginRegion())
+ return OriginMR;
+ }
+ return MR;
+}
+
+std::shared_ptr<PathDiagnosticPiece>
+MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC, BugReport &BR) {
+ // We need only the last move of the reported object's region.
+ // The visitor walks the ExplodedGraph backwards.
+ if (Found)
+ return nullptr;
+ ProgramStateRef State = N->getState();
+ ProgramStateRef StatePrev = N->getFirstPred()->getState();
+ const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region);
+ const RegionState *TrackedObjectPrev =
+ StatePrev->get<TrackedRegionMap>(Region);
+ if (!TrackedObject)
+ return nullptr;
+ if (TrackedObjectPrev && TrackedObject)
+ return nullptr;
+
+ // Retrieve the associated statement.
+ const Stmt *S = PathDiagnosticLocation::getStmt(N);
+ if (!S)
+ return nullptr;
+ Found = true;
+
+ SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+
+ ObjectKind OK = Chk.classifyObject(Region, RD);
+ switch (OK.StdKind) {
+ case SK_SmartPtr:
+ if (MK == MK_Dereference) {
+ OS << "Smart pointer";
+ Chk.explainObject(OS, Region, RD, MK);
+ OS << " is reset to null when moved from";
+ break;
+ }
+
+ // If it's not a dereference, we don't care if it was reset to null
+ // or that it is even a smart pointer.
+ LLVM_FALLTHROUGH;
+ case SK_NonStd:
+ case SK_Safe:
+ OS << "Object";
+ Chk.explainObject(OS, Region, RD, MK);
+ OS << " is moved";
+ break;
+ case SK_Unsafe:
+ OS << "Object";
+ Chk.explainObject(OS, Region, RD, MK);
+ OS << " is left in a valid but unspecified state after move";
+ break;
+ }
+
+ // Generate the extra diagnostic.
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
+ N->getLocationContext());
+ return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true);
+}
+
+const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N,
+ const MemRegion *Region,
+ CheckerContext &C) const {
+ // Walk the ExplodedGraph backwards and find the first node that referred to
+ // the tracked region.
+ const ExplodedNode *MoveNode = N;
+
+ while (N) {
+ ProgramStateRef State = N->getState();
+ if (!State->get<TrackedRegionMap>(Region))
+ break;
+ MoveNode = N;
+ N = N->pred_empty() ? nullptr : *(N->pred_begin());
+ }
+ return MoveNode;
+}
+
+void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region,
+ const CXXRecordDecl *RD, MisuseKind MK,
+ CheckerContext &C) const {
+ assert(!C.isDifferent() && "No transitions should have been made by now");
+ const RegionState *RS = State->get<TrackedRegionMap>(Region);
+ ObjectKind OK = classifyObject(Region, RD);
+
+ // Just in case: if it's not a smart pointer but it does have operator *,
+ // we shouldn't call the bug a dereference.
+ if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr)
+ MK = MK_FunCall;
+
+ if (!RS || !shouldWarnAbout(OK, MK)
+ || isInMoveSafeContext(C.getLocationContext())) {
+ // Finalize changes made by the caller.
+ C.addTransition(State);
+ return;
+ }
+
+ // Don't report it in case if any base region is already reported.
+ // But still generate a sink in case of UB.
+ // And still finalize changes made by the caller.
+ if (isAnyBaseRegionReported(State, Region)) {
+ if (misuseCausesCrash(MK)) {
+ C.generateSink(State, C.getPredecessor());
+ } else {
+ C.addTransition(State);
+ }
+ return;
+ }
+
+ ExplodedNode *N = reportBug(Region, RD, C, MK);
+
+ // If the program has already crashed on this path, don't bother.
+ if (N->isSink())
+ return;
+
+ State = State->set<TrackedRegionMap>(Region, RegionState::getReported());
+ C.addTransition(State, N);
+}
+
+ExplodedNode *MoveChecker::reportBug(const MemRegion *Region,
+ const CXXRecordDecl *RD, CheckerContext &C,
+ MisuseKind MK) const {
+ if (ExplodedNode *N = misuseCausesCrash(MK) ? C.generateErrorNode()
+ : C.generateNonFatalErrorNode()) {
+
+ if (!BT)
+ BT.reset(new BugType(this, "Use-after-move",
+ "C++ move semantics"));
+
+ // Uniqueing report to the same object.
+ PathDiagnosticLocation LocUsedForUniqueing;
+ const ExplodedNode *MoveNode = getMoveLocation(N, Region, C);
+
+ if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode))
+ LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
+ MoveStmt, C.getSourceManager(), MoveNode->getLocationContext());
+
+ // Creating the error message.
+ llvm::SmallString<128> Str;
+ llvm::raw_svector_ostream OS(Str);
+ switch(MK) {
+ case MK_FunCall:
+ OS << "Method called on moved-from object";
+ explainObject(OS, Region, RD, MK);
+ break;
+ case MK_Copy:
+ OS << "Moved-from object";
+ explainObject(OS, Region, RD, MK);
+ OS << " is copied";
+ break;
+ case MK_Move:
+ OS << "Moved-from object";
+ explainObject(OS, Region, RD, MK);
+ OS << " is moved";
+ break;
+ case MK_Dereference:
+ OS << "Dereference of null smart pointer";
+ explainObject(OS, Region, RD, MK);
+ break;
+ }
+
+ auto R =
+ llvm::make_unique<BugReport>(*BT, OS.str(), N, LocUsedForUniqueing,
+ MoveNode->getLocationContext()->getDecl());
+ R->addVisitor(llvm::make_unique<MovedBugVisitor>(*this, Region, RD, MK));
+ C.emitReport(std::move(R));
+ return N;
+ }
+ return nullptr;
+}
+
+void MoveChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ const auto *AFC = dyn_cast<AnyFunctionCall>(&Call);
+ if (!AFC)
+ return;
+
+ ProgramStateRef State = C.getState();
+ const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl());
+ if (!MethodDecl)
+ return;
+
+ // Check if an object became moved-from.
+ // Object can become moved from after a call to move assignment operator or
+ // move constructor .
+ const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl);
+ if (ConstructorDecl && !ConstructorDecl->isMoveConstructor())
+ return;
+
+ if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator())
+ return;
+
+ const auto ArgRegion = AFC->getArgSVal(0).getAsRegion();
+ if (!ArgRegion)
+ return;
+
+ // Skip moving the object to itself.
+ const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call);
+ if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion)
+ return;
+
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC))
+ if (IC->getCXXThisVal().getAsRegion() == ArgRegion)
+ return;
+
+ const MemRegion *BaseRegion = ArgRegion->getBaseRegion();
+ // Skip temp objects because of their short lifetime.
+ if (BaseRegion->getAs<CXXTempObjectRegion>() ||
+ AFC->getArgExpr(0)->isRValue())
+ return;
+ // If it has already been reported do not need to modify the state.
+
+ if (State->get<TrackedRegionMap>(ArgRegion))
+ return;
+
+ const CXXRecordDecl *RD = MethodDecl->getParent();
+ ObjectKind OK = classifyObject(ArgRegion, RD);
+ if (shouldBeTracked(OK)) {
+ // Mark object as moved-from.
+ State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved());
+ C.addTransition(State);
+ return;
+ }
+ assert(!C.isDifferent() && "Should not have made transitions on this path!");
+}
+
+bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const {
+ // We abandon the cases where bool/void/void* conversion happens.
+ if (const auto *ConversionDec =
+ dyn_cast_or_null<CXXConversionDecl>(MethodDec)) {
+ const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull();
+ if (!Tp)
+ return false;
+ if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType())
+ return true;
+ }
+ // Function call `empty` can be skipped.
+ return (MethodDec && MethodDec->getDeclName().isIdentifier() &&
+ (MethodDec->getName().lower() == "empty" ||
+ MethodDec->getName().lower() == "isempty"));
+}
+
+bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const {
+ if (!MethodDec)
+ return false;
+ if (MethodDec->hasAttr<ReinitializesAttr>())
+ return true;
+ if (MethodDec->getDeclName().isIdentifier()) {
+ std::string MethodName = MethodDec->getName().lower();
+ // TODO: Some of these methods (eg., resize) are not always resetting
+ // the state, so we should consider looking at the arguments.
+ if (MethodName == "reset" || MethodName == "clear" ||
+ MethodName == "destroy" || MethodName == "resize" ||
+ MethodName == "shrink")
+ return true;
+ }
+ return false;
+}
+
+// Don't report an error inside a move related operation.
+// We assume that the programmer knows what she does.
+bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const {
+ do {
+ const auto *CtxDec = LC->getDecl();
+ auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec);
+ auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec);
+ auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec);
+ if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) ||
+ (MethodDec && MethodDec->isOverloadedOperator() &&
+ MethodDec->getOverloadedOperator() == OO_Equal) ||
+ isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec))
+ return true;
+ } while ((LC = LC->getParent()));
+ return false;
+}
+
+bool MoveChecker::belongsTo(const CXXRecordDecl *RD,
+ const llvm::StringSet<> &Set) const {
+ const IdentifierInfo *II = RD->getIdentifier();
+ return II && Set.count(II->getName());
+}
+
+MoveChecker::ObjectKind
+MoveChecker::classifyObject(const MemRegion *MR,
+ const CXXRecordDecl *RD) const {
+ // Local variables and local rvalue references are classified as "Local".
+ // For the purposes of this checker, we classify move-safe STL types
+ // as not-"STL" types, because that's how the checker treats them.
+ MR = unwrapRValueReferenceIndirection(MR);
+ bool IsLocal =
+ MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace());
+
+ if (!RD || !RD->getDeclContext()->isStdNamespace())
+ return { IsLocal, SK_NonStd };
+
+ if (belongsTo(RD, StdSmartPtrClasses))
+ return { IsLocal, SK_SmartPtr };
+
+ if (belongsTo(RD, StdSafeClasses))
+ return { IsLocal, SK_Safe };
+
+ return { IsLocal, SK_Unsafe };
+}
+
+void MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR,
+ const CXXRecordDecl *RD, MisuseKind MK) const {
+ // We may need a leading space every time we actually explain anything,
+ // and we never know if we are to explain anything until we try.
+ if (const auto DR =
+ dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection(MR))) {
+ const auto *RegionDecl = cast<NamedDecl>(DR->getDecl());
+ OS << " '" << RegionDecl->getNameAsString() << "'";
+ }
+
+ ObjectKind OK = classifyObject(MR, RD);
+ switch (OK.StdKind) {
+ case SK_NonStd:
+ case SK_Safe:
+ break;
+ case SK_SmartPtr:
+ if (MK != MK_Dereference)
+ break;
+
+ // We only care about the type if it's a dereference.
+ LLVM_FALLTHROUGH;
+ case SK_Unsafe:
+ OS << " of type '" << RD->getQualifiedNameAsString() << "'";
+ break;
+ };
+}
+
+void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ // Remove the MemRegions from the map on which a ctor/dtor call or assignment
+ // happened.
+
+ // Checking constructor calls.
+ if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
+ State = removeFromState(State, CC->getCXXThisVal().getAsRegion());
+ auto CtorDec = CC->getDecl();
+ // Check for copying a moved-from object and report the bug.
+ if (CtorDec && CtorDec->isCopyOrMoveConstructor()) {
+ const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion();
+ const CXXRecordDecl *RD = CtorDec->getParent();
+ MisuseKind MK = CtorDec->isMoveConstructor() ? MK_Move : MK_Copy;
+ modelUse(State, ArgRegion, RD, MK, C);
+ return;
+ }
+ }
+
+ const auto IC = dyn_cast<CXXInstanceCall>(&Call);
+ if (!IC)
+ return;
+
+ // Calling a destructor on a moved object is fine.
+ if (isa<CXXDestructorCall>(IC))
+ return;
+
+ const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
+ if (!ThisRegion)
+ return;
+
+ // The remaining part is check only for method call on a moved-from object.
+ const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl());
+ if (!MethodDecl)
+ return;
+
+ // We want to investigate the whole object, not only sub-object of a parent
+ // class in which the encountered method defined.
+ ThisRegion = ThisRegion->getMostDerivedObjectRegion();
+
+ if (isStateResetMethod(MethodDecl)) {
+ State = removeFromState(State, ThisRegion);
+ C.addTransition(State);
+ return;
+ }
+
+ if (isMoveSafeMethod(MethodDecl))
+ return;
+
+ // Store class declaration as well, for bug reporting purposes.
+ const CXXRecordDecl *RD = MethodDecl->getParent();
+
+ if (MethodDecl->isOverloadedOperator()) {
+ OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator();
+
+ if (OOK == OO_Equal) {
+ // Remove the tracked object for every assignment operator, but report bug
+ // only for move or copy assignment's argument.
+ State = removeFromState(State, ThisRegion);
+
+ if (MethodDecl->isCopyAssignmentOperator() ||
+ MethodDecl->isMoveAssignmentOperator()) {
+ const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion();
+ MisuseKind MK =
+ MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy;
+ modelUse(State, ArgRegion, RD, MK, C);
+ return;
+ }
+ C.addTransition(State);
+ return;
+ }
+
+ if (OOK == OO_Star || OOK == OO_Arrow) {
+ modelUse(State, ThisRegion, RD, MK_Dereference, C);
+ return;
+ }
+ }
+
+ modelUse(State, ThisRegion, RD, MK_FunCall, C);
+}
+
+void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
+ for (TrackedRegionMapTy::value_type E : TrackedRegions) {
+ const MemRegion *Region = E.first;
+ bool IsRegDead = !SymReaper.isLiveRegion(Region);
+
+ // Remove the dead regions from the region map.
+ if (IsRegDead) {
+ State = State->remove<TrackedRegionMap>(Region);
+ }
+ }
+ C.addTransition(State);
+}
+
+ProgramStateRef MoveChecker::checkRegionChanges(
+ ProgramStateRef State, const InvalidatedSymbols *Invalidated,
+ ArrayRef<const MemRegion *> RequestedRegions,
+ ArrayRef<const MemRegion *> InvalidatedRegions,
+ const LocationContext *LCtx, const CallEvent *Call) const {
+ if (Call) {
+ // Relax invalidation upon function calls: only invalidate parameters
+ // that are passed directly via non-const pointers or non-const references
+ // or rvalue references.
+ // In case of an InstanceCall don't invalidate the this-region since
+ // it is fully handled in checkPreCall and checkPostCall.
+ const MemRegion *ThisRegion = nullptr;
+ if (const auto *IC = dyn_cast<CXXInstanceCall>(Call))
+ ThisRegion = IC->getCXXThisVal().getAsRegion();
+
+ // Requested ("explicit") regions are the regions passed into the call
+ // directly, but not all of them end up being invalidated.
+ // But when they do, they appear in the InvalidatedRegions array as well.
+ for (const auto *Region : RequestedRegions) {
+ if (ThisRegion != Region) {
+ if (llvm::find(InvalidatedRegions, Region) !=
+ std::end(InvalidatedRegions)) {
+ State = removeFromState(State, Region);
+ }
+ }
+ }
+ } else {
+ // For invalidations that aren't caused by calls, assume nothing. In
+ // particular, direct write into an object's field invalidates the status.
+ for (const auto *Region : InvalidatedRegions)
+ State = removeFromState(State, Region->getBaseRegion());
+ }
+
+ return State;
+}
+
+void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+
+ TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
+
+ if (!RS.isEmpty()) {
+ Out << Sep << "Moved-from objects :" << NL;
+ for (auto I: RS) {
+ I.first->dumpToStream(Out);
+ if (I.second.isMoved())
+ Out << ": moved";
+ else
+ Out << ": moved and reported";
+ Out << NL;
+ }
+ }
+}
+void ento::registerMoveChecker(CheckerManager &mgr) {
+ MoveChecker *chk = mgr.registerChecker<MoveChecker>();
+ chk->setAggressiveness(
+ mgr.getAnalyzerOptions().getCheckerStringOption("WarnOn", "", chk));
+}
diff --git a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
index 0e7894788c87..4ed1b25cb09e 100644
--- a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
index 2bd68b625c1f..06c43c6b9470 100644
--- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
diff --git a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
index 8a5c769b6b50..83d4b5b0758b 100644
--- a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp
@@ -12,9 +12,9 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
-#include "SelectorExtras.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
+#include "clang/Analysis/SelectorExtras.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
diff --git a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
index 01d2c0491b85..3c4363b6850e 100644
--- a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -192,7 +192,7 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode,
*BTAttrNonNull,
"Null pointer passed as an argument to a 'nonnull' parameter", ErrorNode);
if (ArgE)
- bugreporter::trackNullOrUndefValue(ErrorNode, ArgE, *R);
+ bugreporter::trackExpressionValue(ErrorNode, ArgE, *R);
return R;
}
@@ -208,9 +208,7 @@ std::unique_ptr<BugReport> NonNullParamChecker::genReportReferenceToNullPointer(
const Expr *ArgEDeref = bugreporter::getDerefExpr(ArgE);
if (!ArgEDeref)
ArgEDeref = ArgE;
- bugreporter::trackNullOrUndefValue(ErrorNode,
- ArgEDeref,
- *R);
+ bugreporter::trackExpressionValue(ErrorNode, ArgEDeref, *R);
}
return R;
diff --git a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp
index 6f3180eb839a..ce9e950aa9ba 100644
--- a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp
@@ -21,7 +21,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
index 7d1ca61c97a9..e535d1ae27ac 100644
--- a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp
@@ -25,7 +25,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -139,7 +139,6 @@ private:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -175,7 +174,8 @@ private:
if (Error == ErrorKind::NilAssignedToNonnull ||
Error == ErrorKind::NilPassedToNonnull ||
Error == ErrorKind::NilReturnedToNonnull)
- bugreporter::trackNullOrUndefValue(N, ValueExpr, *R);
+ if (const auto *Ex = dyn_cast<Expr>(ValueExpr))
+ bugreporter::trackExpressionValue(N, Ex, *R);
}
BR.emitReport(std::move(R));
}
@@ -185,7 +185,7 @@ private:
const SymbolicRegion *getTrackRegion(SVal Val,
bool CheckSuperRegion = false) const;
- /// Returns true if the call is diagnosable in the currrent analyzer
+ /// Returns true if the call is diagnosable in the current analyzer
/// configuration.
bool isDiagnosableCall(const CallEvent &Call) const {
if (NoDiagnoseCallsToSystemHeaders && Call.isInSystemHeader())
@@ -293,11 +293,10 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const {
std::shared_ptr<PathDiagnosticPiece>
NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) {
ProgramStateRef State = N->getState();
- ProgramStateRef StatePrev = PrevN->getState();
+ ProgramStateRef StatePrev = N->getFirstPred()->getState();
const NullabilityState *TrackedNullab = State->get<NullabilityMap>(Region);
const NullabilityState *TrackedNullabPrev =
@@ -311,7 +310,7 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N,
// Retrieve the associated statement.
const Stmt *S = TrackedNullab->getNullabilitySource();
- if (!S || S->getLocStart().isInvalid()) {
+ if (!S || S->getBeginLoc().isInvalid()) {
S = PathDiagnosticLocation::getStmt(N);
}
@@ -330,8 +329,8 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N,
nullptr);
}
-/// Returns true when the value stored at the given location is null
-/// and the passed in type is nonnnull.
+/// Returns true when the value stored at the given location has been
+/// constrained to null after being passed through an object of nonnnull type.
static bool checkValueAtLValForInvariantViolation(ProgramStateRef State,
SVal LV, QualType T) {
if (getNullabilityAnnotation(T) != Nullability::Nonnull)
@@ -341,9 +340,14 @@ static bool checkValueAtLValForInvariantViolation(ProgramStateRef State,
if (!RegionVal)
return false;
- auto StoredVal =
- State->getSVal(RegionVal->getRegion()).getAs<DefinedOrUnknownSVal>();
- if (!StoredVal)
+ // If the value was constrained to null *after* it was passed through that
+ // location, it could not have been a concrete pointer *when* it was passed.
+ // In that case we would have handled the situation when the value was
+ // bound to that location, by emitting (or not emitting) a report.
+ // Therefore we are only interested in symbolic regions that can be either
+ // null or non-null depending on the value of their respective symbol.
+ auto StoredVal = State->getSVal(*RegionVal).getAs<loc::MemRegionVal>();
+ if (!StoredVal || !isa<SymbolicRegion>(StoredVal->getRegion()))
return false;
if (getNullConstraint(*StoredVal, State) == NullConstraint::IsNull)
@@ -447,9 +451,6 @@ void NullabilityChecker::reportBugIfInvariantHolds(StringRef Msg,
/// Cleaning up the program state.
void NullabilityChecker::checkDeadSymbols(SymbolReaper &SR,
CheckerContext &C) const {
- if (!SR.hasDeadSymbols())
- return;
-
ProgramStateRef State = C.getState();
NullabilityMapTy Nullabilities = State->get<NullabilityMap>();
for (NullabilityMapTy::iterator I = Nullabilities.begin(),
@@ -766,7 +767,7 @@ void NullabilityChecker::checkPostCall(const CallEvent &Call,
// CG headers are misannotated. Do not warn for symbols that are the results
// of CG calls.
const SourceManager &SM = C.getSourceManager();
- StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getLocStart()));
+ StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getBeginLoc()));
if (llvm::sys::path::filename(FilePath).startswith("CG")) {
State = State->set<NullabilityMap>(Region, Nullability::Contradicted);
C.addTransition(State);
@@ -1174,10 +1175,15 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State,
NullabilityMapTy B = State->get<NullabilityMap>();
+ if (State->get<InvariantViolated>())
+ Out << Sep << NL
+ << "Nullability invariant was violated, warnings suppressed." << NL;
+
if (B.isEmpty())
return;
- Out << Sep << NL;
+ if (!State->get<InvariantViolated>())
+ Out << Sep << NL;
for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
Out << I->first << " : ";
@@ -1194,7 +1200,7 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State,
checker->NeedTracking = checker->NeedTracking || trackingRequired; \
checker->NoDiagnoseCallsToSystemHeaders = \
checker->NoDiagnoseCallsToSystemHeaders || \
- mgr.getAnalyzerOptions().getBooleanOption( \
+ mgr.getAnalyzerOptions().getCheckerBooleanOption( \
"NoDiagnoseCallsToSystemHeaders", false, checker, true); \
}
diff --git a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp
index d1749cfdbe27..4e3a7205f1f4 100644
--- a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp
@@ -26,7 +26,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -77,7 +77,7 @@ void Callback::run(const MatchFinder::MatchResult &Result) {
// to zero literals in non-pedantic mode.
// FIXME: Introduce an AST matcher to implement the macro-related logic?
bool MacroIndicatesWeShouldSkipTheCheck = false;
- SourceLocation Loc = CheckIfNull->getLocStart();
+ SourceLocation Loc = CheckIfNull->getBeginLoc();
if (Loc.isMacroID()) {
StringRef MacroName = Lexer::getImmediateMacroName(
Loc, ACtx.getSourceManager(), ACtx.getLangOpts());
@@ -87,9 +87,10 @@ void Callback::run(const MatchFinder::MatchResult &Result) {
MacroIndicatesWeShouldSkipTheCheck = true;
}
if (!MacroIndicatesWeShouldSkipTheCheck) {
- llvm::APSInt Result;
+ Expr::EvalResult EVResult;
if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt(
- Result, ACtx, Expr::SE_AllowSideEffects)) {
+ EVResult, ACtx, Expr::SE_AllowSideEffects)) {
+ llvm::APSInt Result = EVResult.Val.getInt();
if (Result == 0) {
if (!C->Pedantic)
return;
@@ -346,5 +347,5 @@ void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) {
NumberObjectConversionChecker *Chk =
Mgr.registerChecker<NumberObjectConversionChecker>();
Chk->Pedantic =
- Mgr.getAnalyzerOptions().getBooleanOption("Pedantic", false, Chk);
+ Mgr.getAnalyzerOptions().getCheckerBooleanOption("Pedantic", false, Chk);
}
diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
index b7339fe79f69..185b57575cb0 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/StmtObjC.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -49,7 +49,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S,
"for @synchronized"));
auto report =
llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N);
- bugreporter::trackNullOrUndefValue(N, Ex, *report);
+ bugreporter::trackExpressionValue(N, Ex, *report);
C.emitReport(std::move(report));
}
return;
@@ -73,7 +73,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S,
"(no synchronization will occur)"));
auto report =
llvm::make_unique<BugReport>(*BT_null, BT_null->getDescription(), N);
- bugreporter::trackNullOrUndefValue(N, Ex, *report);
+ bugreporter::trackExpressionValue(N, Ex, *report);
C.emitReport(std::move(report));
return;
@@ -89,6 +89,6 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S,
}
void ento::registerObjCAtSyncChecker(CheckerManager &mgr) {
- if (mgr.getLangOpts().ObjC2)
+ if (mgr.getLangOpts().ObjC)
mgr.registerChecker<ObjCAtSyncChecker>();
}
diff --git a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
index 81bcda51b8f8..0424958f8e65 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp
@@ -27,7 +27,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
index e4737fcee7fb..34ce47823d51 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp
@@ -11,7 +11,7 @@
// 'CFDictionary', 'CFSet' APIs.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/StmtVisitor.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/TargetInfo.h"
diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
index fb05ca630b45..1c8c0d8dedda 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp
@@ -16,7 +16,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -57,6 +57,9 @@ public:
const InvalidatedSymbols &Escaped,
const CallEvent *Call,
PointerEscapeKind Kind) const;
+
+ void printState(raw_ostream &OS, ProgramStateRef State,
+ const char *NL, const char *Sep) const;
};
} // end anonymous namespace
@@ -144,6 +147,8 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE,
initBugType();
auto R = llvm::make_unique<BugReport>(*BT, "Index is out of bounds", N);
R->addRange(IdxExpr->getSourceRange());
+ bugreporter::trackExpressionValue(N, IdxExpr, *R,
+ /*EnableNullFPSuppression=*/false);
C.emitReport(std::move(R));
return;
}
@@ -166,6 +171,18 @@ ObjCContainersChecker::checkPointerEscape(ProgramStateRef State,
return State;
}
+void ObjCContainersChecker::printState(raw_ostream &OS, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+ ArraySizeMapTy Map = State->get<ArraySizeMap>();
+ if (Map.isEmpty())
+ return;
+
+ OS << Sep << "ObjC container sizes :" << NL;
+ for (auto I : Map) {
+ OS << I.first << " : " << I.second << NL;
+ }
+}
+
/// Register checker.
void ento::registerObjCContainersChecker(CheckerManager &mgr) {
mgr.registerChecker<ObjCContainersChecker>();
diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
index d01c6ae6e093..d383302b2790 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprObjC.h"
diff --git a/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp
index dfd2c9afe7fb..018d3fcfceb9 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
index 629520437369..efa804220765 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp
@@ -36,7 +36,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
index fcba3b33f3e0..9058784dd345 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
@@ -72,7 +72,6 @@ public:
Satisfied(false) {}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -247,8 +246,7 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const {
std::shared_ptr<PathDiagnosticPiece>
SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
- BugReporterContext &BRC, BugReport &BR) {
+ BugReporterContext &BRC, BugReport &) {
if (Satisfied)
return nullptr;
@@ -257,7 +255,8 @@ SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ,
bool CalledNow =
Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
bool CalledBefore =
- Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol);
+ Succ->getFirstPred()->getState()->contains<CalledSuperDealloc>(
+ ReceiverSymbol);
// Is Succ the node on which the analyzer noted that [super dealloc] was
// called on ReceiverSymbol?
diff --git a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp
index c6da37eac0c0..7f7b45316087 100644
--- a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/Expr.h"
@@ -98,7 +98,7 @@ static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID,
SourceManager &SM) {
for (const auto *I : C->decls())
if (const auto *FD = dyn_cast<FunctionDecl>(I)) {
- SourceLocation L = FD->getLocStart();
+ SourceLocation L = FD->getBeginLoc();
if (SM.getFileID(L) == FID)
Scan(M, FD->getBody());
}
diff --git a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
index f69f3492edb1..211db392bf71 100644
--- a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/RecordLayout.h"
@@ -41,7 +41,8 @@ public:
BugReporter &BRArg) const {
BR = &BRArg;
AllowedPad =
- MGR.getAnalyzerOptions().getOptionAsInteger("AllowedPad", 24, this);
+ MGR.getAnalyzerOptions()
+ .getCheckerIntegerOption("AllowedPad", 24, this);
assert(AllowedPad >= 0 && "AllowedPad option should be non-negative");
// The calls to checkAST* from AnalysisConsumer don't
@@ -75,6 +76,20 @@ public:
if (shouldSkipDecl(RD))
return;
+ // TODO: Figure out why we are going through declarations and not only
+ // definitions.
+ if (!(RD = RD->getDefinition()))
+ return;
+
+ // This is the simplest correct case: a class with no fields and one base
+ // class. Other cases are more complicated because of how the base classes
+ // & fields might interact, so we don't bother dealing with them.
+ // TODO: Support other combinations of base classes and fields.
+ if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD))
+ if (CXXRD->field_empty() && CXXRD->getNumBases() == 1)
+ return visitRecord(CXXRD->bases().begin()->getType()->getAsRecordDecl(),
+ PadMultiplier);
+
auto &ASTContext = RD->getASTContext();
const ASTRecordLayout &RL = ASTContext.getASTRecordLayout(RD);
assert(llvm::isPowerOf2_64(RL.getAlignment().getQuantity()));
@@ -112,12 +127,15 @@ public:
if (RT == nullptr)
return;
- // TODO: Recurse into the fields and base classes to see if any
- // of those have excess padding.
+ // TODO: Recurse into the fields to see if they have excess padding.
visitRecord(RT->getDecl(), Elts);
}
bool shouldSkipDecl(const RecordDecl *RD) const {
+ // TODO: Figure out why we are going through declarations and not only
+ // definitions.
+ if (!(RD = RD->getDefinition()))
+ return true;
auto Location = RD->getLocation();
// If the construct doesn't have a source file, then it's not something
// we want to diagnose.
@@ -132,13 +150,14 @@ public:
// Not going to attempt to optimize unions.
if (RD->isUnion())
return true;
- // How do you reorder fields if you haven't got any?
- if (RD->field_empty())
- return true;
if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) {
// Tail padding with base classes ends up being very complicated.
- // We will skip objects with base classes for now.
- if (CXXRD->getNumBases() != 0)
+ // We will skip objects with base classes for now, unless they do not
+ // have fields.
+ // TODO: Handle more base class scenarios.
+ if (!CXXRD->field_empty() && CXXRD->getNumBases() != 0)
+ return true;
+ if (CXXRD->field_empty() && CXXRD->getNumBases() != 1)
return true;
// Virtual bases are complicated, skipping those for now.
if (CXXRD->getNumVBases() != 0)
@@ -150,6 +169,10 @@ public:
if (CXXRD->getTypeForDecl()->isInstantiationDependentType())
return true;
}
+ // How do you reorder fields if you haven't got any?
+ else if (RD->field_empty())
+ return true;
+
auto IsTrickyField = [](const FieldDecl *FD) -> bool {
// Bitfield layout is hard.
if (FD->isBitField())
@@ -237,7 +260,7 @@ public:
};
std::transform(RD->field_begin(), RD->field_end(),
std::back_inserter(Fields), GatherSizesAndAlignments);
- llvm::sort(Fields.begin(), Fields.end());
+ llvm::sort(Fields);
// This lets us skip over vptrs and non-virtual bases,
// so that we can just worry about the fields in our object.
// Note that this does cause us to miss some cases where we
@@ -323,7 +346,7 @@ public:
BR->emitReport(std::move(Report));
}
};
-}
+} // namespace
void ento::registerPaddingChecker(CheckerManager &Mgr) {
Mgr.registerChecker<PaddingChecker>();
diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
index 63f82b275ba2..de3a16ebc729 100644
--- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -112,7 +112,7 @@ PointerArithChecker::getPointedRegion(const MemRegion *Region,
}
/// Checks whether a region is the part of an array.
-/// In case there is a dericed to base cast above the array element, the
+/// In case there is a derived to base cast above the array element, the
/// Polymorphic output value is set to true. AKind output value is set to the
/// allocation kind of the inspected region.
const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region,
diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
index 9aa5348e4c34..41490e45f241 100644
--- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
index 10ab952e069b..66cc37278809 100644
--- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
deleted file mode 100644
index 9c85c0983723..000000000000
--- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp
+++ /dev/null
@@ -1,4156 +0,0 @@
-//==-- RetainCountChecker.cpp - Checks for leaks and other issues -*- 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 methods for RetainCountChecker, which implements
-// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
-//
-//===----------------------------------------------------------------------===//
-
-#include "AllocationDiagnostics.h"
-#include "ClangSACheckers.h"
-#include "SelectorExtras.h"
-#include "clang/AST/Attr.h"
-#include "clang/AST/DeclCXX.h"
-#include "clang/AST/DeclObjC.h"
-#include "clang/AST/ParentMap.h"
-#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
-#include "clang/Basic/LangOptions.h"
-#include "clang/Basic/SourceManager.h"
-#include "clang/StaticAnalyzer/Checkers/ObjCRetainCount.h"
-#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
-#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
-#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/CheckerManager.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/FoldingSet.h"
-#include "llvm/ADT/ImmutableList.h"
-#include "llvm/ADT/ImmutableMap.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SmallString.h"
-#include "llvm/ADT/StringExtras.h"
-#include <cstdarg>
-#include <utility>
-
-using namespace clang;
-using namespace ento;
-using namespace objc_retain;
-using llvm::StrInStrNoCase;
-
-//===----------------------------------------------------------------------===//
-// Adapters for FoldingSet.
-//===----------------------------------------------------------------------===//
-
-namespace llvm {
-template <> struct FoldingSetTrait<ArgEffect> {
-static inline void Profile(const ArgEffect X, FoldingSetNodeID &ID) {
- ID.AddInteger((unsigned) X);
-}
-};
-template <> struct FoldingSetTrait<RetEffect> {
- static inline void Profile(const RetEffect &X, FoldingSetNodeID &ID) {
- ID.AddInteger((unsigned) X.getKind());
- ID.AddInteger((unsigned) X.getObjKind());
-}
-};
-} // end llvm namespace
-
-//===----------------------------------------------------------------------===//
-// Reference-counting logic (typestate + counts).
-//===----------------------------------------------------------------------===//
-
-/// ArgEffects summarizes the effects of a function/method call on all of
-/// its arguments.
-typedef llvm::ImmutableMap<unsigned,ArgEffect> ArgEffects;
-
-namespace {
-class RefVal {
-public:
- enum Kind {
- Owned = 0, // Owning reference.
- NotOwned, // Reference is not owned by still valid (not freed).
- Released, // Object has been released.
- ReturnedOwned, // Returned object passes ownership to caller.
- ReturnedNotOwned, // Return object does not pass ownership to caller.
- ERROR_START,
- ErrorDeallocNotOwned, // -dealloc called on non-owned object.
- ErrorDeallocGC, // Calling -dealloc with GC enabled.
- ErrorUseAfterRelease, // Object used after released.
- ErrorReleaseNotOwned, // Release of an object that was not owned.
- ERROR_LEAK_START,
- ErrorLeak, // A memory leak due to excessive reference counts.
- ErrorLeakReturned, // A memory leak due to the returning method not having
- // the correct naming conventions.
- ErrorGCLeakReturned,
- ErrorOverAutorelease,
- ErrorReturnedNotOwned
- };
-
- /// Tracks how an object referenced by an ivar has been used.
- ///
- /// This accounts for us not knowing if an arbitrary ivar is supposed to be
- /// stored at +0 or +1.
- enum class IvarAccessHistory {
- None,
- AccessedDirectly,
- ReleasedAfterDirectAccess
- };
-
-private:
- /// The number of outstanding retains.
- unsigned Cnt;
- /// The number of outstanding autoreleases.
- unsigned ACnt;
- /// The (static) type of the object at the time we started tracking it.
- QualType T;
-
- /// The current state of the object.
- ///
- /// See the RefVal::Kind enum for possible values.
- unsigned RawKind : 5;
-
- /// The kind of object being tracked (CF or ObjC), if known.
- ///
- /// See the RetEffect::ObjKind enum for possible values.
- unsigned RawObjectKind : 2;
-
- /// True if the current state and/or retain count may turn out to not be the
- /// best possible approximation of the reference counting state.
- ///
- /// If true, the checker may decide to throw away ("override") this state
- /// in favor of something else when it sees the object being used in new ways.
- ///
- /// This setting should not be propagated to state derived from this state.
- /// Once we start deriving new states, it would be inconsistent to override
- /// them.
- unsigned RawIvarAccessHistory : 2;
-
- RefVal(Kind k, RetEffect::ObjKind o, unsigned cnt, unsigned acnt, QualType t,
- IvarAccessHistory IvarAccess)
- : Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)),
- RawObjectKind(static_cast<unsigned>(o)),
- RawIvarAccessHistory(static_cast<unsigned>(IvarAccess)) {
- assert(getKind() == k && "not enough bits for the kind");
- assert(getObjKind() == o && "not enough bits for the object kind");
- assert(getIvarAccessHistory() == IvarAccess && "not enough bits");
- }
-
-public:
- Kind getKind() const { return static_cast<Kind>(RawKind); }
-
- RetEffect::ObjKind getObjKind() const {
- return static_cast<RetEffect::ObjKind>(RawObjectKind);
- }
-
- unsigned getCount() const { return Cnt; }
- unsigned getAutoreleaseCount() const { return ACnt; }
- unsigned getCombinedCounts() const { return Cnt + ACnt; }
- void clearCounts() {
- Cnt = 0;
- ACnt = 0;
- }
- void setCount(unsigned i) {
- Cnt = i;
- }
- void setAutoreleaseCount(unsigned i) {
- ACnt = i;
- }
-
- QualType getType() const { return T; }
-
- /// Returns what the analyzer knows about direct accesses to a particular
- /// instance variable.
- ///
- /// If the object with this refcount wasn't originally from an Objective-C
- /// ivar region, this should always return IvarAccessHistory::None.
- IvarAccessHistory getIvarAccessHistory() const {
- return static_cast<IvarAccessHistory>(RawIvarAccessHistory);
- }
-
- bool isOwned() const {
- return getKind() == Owned;
- }
-
- bool isNotOwned() const {
- return getKind() == NotOwned;
- }
-
- bool isReturnedOwned() const {
- return getKind() == ReturnedOwned;
- }
-
- bool isReturnedNotOwned() const {
- return getKind() == ReturnedNotOwned;
- }
-
- /// Create a state for an object whose lifetime is the responsibility of the
- /// current function, at least partially.
- ///
- /// Most commonly, this is an owned object with a retain count of +1.
- static RefVal makeOwned(RetEffect::ObjKind o, QualType t,
- unsigned Count = 1) {
- return RefVal(Owned, o, Count, 0, t, IvarAccessHistory::None);
- }
-
- /// Create a state for an object whose lifetime is not the responsibility of
- /// the current function.
- ///
- /// Most commonly, this is an unowned object with a retain count of +0.
- static RefVal makeNotOwned(RetEffect::ObjKind o, QualType t,
- unsigned Count = 0) {
- return RefVal(NotOwned, o, Count, 0, t, IvarAccessHistory::None);
- }
-
- RefVal operator-(size_t i) const {
- return RefVal(getKind(), getObjKind(), getCount() - i,
- getAutoreleaseCount(), getType(), getIvarAccessHistory());
- }
-
- RefVal operator+(size_t i) const {
- return RefVal(getKind(), getObjKind(), getCount() + i,
- getAutoreleaseCount(), getType(), getIvarAccessHistory());
- }
-
- RefVal operator^(Kind k) const {
- return RefVal(k, getObjKind(), getCount(), getAutoreleaseCount(),
- getType(), getIvarAccessHistory());
- }
-
- RefVal autorelease() const {
- return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount()+1,
- getType(), getIvarAccessHistory());
- }
-
- RefVal withIvarAccess() const {
- assert(getIvarAccessHistory() == IvarAccessHistory::None);
- return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
- getType(), IvarAccessHistory::AccessedDirectly);
- }
-
- RefVal releaseViaIvar() const {
- assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly);
- return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
- getType(), IvarAccessHistory::ReleasedAfterDirectAccess);
- }
-
- // Comparison, profiling, and pretty-printing.
-
- bool hasSameState(const RefVal &X) const {
- return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt &&
- getIvarAccessHistory() == X.getIvarAccessHistory();
- }
-
- bool operator==(const RefVal& X) const {
- return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind();
- }
-
- void Profile(llvm::FoldingSetNodeID& ID) const {
- ID.Add(T);
- ID.AddInteger(RawKind);
- ID.AddInteger(Cnt);
- ID.AddInteger(ACnt);
- ID.AddInteger(RawObjectKind);
- ID.AddInteger(RawIvarAccessHistory);
- }
-
- void print(raw_ostream &Out) const;
-};
-
-void RefVal::print(raw_ostream &Out) const {
- if (!T.isNull())
- Out << "Tracked " << T.getAsString() << '/';
-
- switch (getKind()) {
- default: llvm_unreachable("Invalid RefVal kind");
- case Owned: {
- Out << "Owned";
- unsigned cnt = getCount();
- if (cnt) Out << " (+ " << cnt << ")";
- break;
- }
-
- case NotOwned: {
- Out << "NotOwned";
- unsigned cnt = getCount();
- if (cnt) Out << " (+ " << cnt << ")";
- break;
- }
-
- case ReturnedOwned: {
- Out << "ReturnedOwned";
- unsigned cnt = getCount();
- if (cnt) Out << " (+ " << cnt << ")";
- break;
- }
-
- case ReturnedNotOwned: {
- Out << "ReturnedNotOwned";
- unsigned cnt = getCount();
- if (cnt) Out << " (+ " << cnt << ")";
- break;
- }
-
- case Released:
- Out << "Released";
- break;
-
- case ErrorDeallocGC:
- Out << "-dealloc (GC)";
- break;
-
- case ErrorDeallocNotOwned:
- Out << "-dealloc (not-owned)";
- break;
-
- case ErrorLeak:
- Out << "Leaked";
- break;
-
- case ErrorLeakReturned:
- Out << "Leaked (Bad naming)";
- break;
-
- case ErrorGCLeakReturned:
- Out << "Leaked (GC-ed at return)";
- break;
-
- case ErrorUseAfterRelease:
- Out << "Use-After-Release [ERROR]";
- break;
-
- case ErrorReleaseNotOwned:
- Out << "Release of Not-Owned [ERROR]";
- break;
-
- case RefVal::ErrorOverAutorelease:
- Out << "Over-autoreleased";
- break;
-
- case RefVal::ErrorReturnedNotOwned:
- Out << "Non-owned object returned instead of owned";
- break;
- }
-
- switch (getIvarAccessHistory()) {
- case IvarAccessHistory::None:
- break;
- case IvarAccessHistory::AccessedDirectly:
- Out << " [direct ivar access]";
- break;
- case IvarAccessHistory::ReleasedAfterDirectAccess:
- Out << " [released after direct ivar access]";
- }
-
- if (ACnt) {
- Out << " [autorelease -" << ACnt << ']';
- }
-}
-} //end anonymous namespace
-
-//===----------------------------------------------------------------------===//
-// RefBindings - State used to track object reference counts.
-//===----------------------------------------------------------------------===//
-
-REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal)
-
-static inline const RefVal *getRefBinding(ProgramStateRef State,
- SymbolRef Sym) {
- return State->get<RefBindings>(Sym);
-}
-
-static inline ProgramStateRef setRefBinding(ProgramStateRef State,
- SymbolRef Sym, RefVal Val) {
- return State->set<RefBindings>(Sym, Val);
-}
-
-static ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) {
- return State->remove<RefBindings>(Sym);
-}
-
-//===----------------------------------------------------------------------===//
-// Function/Method behavior summaries.
-//===----------------------------------------------------------------------===//
-
-namespace {
-class RetainSummary {
- /// Args - a map of (index, ArgEffect) pairs, where index
- /// specifies the argument (starting from 0). This can be sparsely
- /// populated; arguments with no entry in Args use 'DefaultArgEffect'.
- ArgEffects Args;
-
- /// DefaultArgEffect - The default ArgEffect to apply to arguments that
- /// do not have an entry in Args.
- ArgEffect DefaultArgEffect;
-
- /// Receiver - If this summary applies to an Objective-C message expression,
- /// this is the effect applied to the state of the receiver.
- ArgEffect Receiver;
-
- /// Ret - The effect on the return value. Used to indicate if the
- /// function/method call returns a new tracked symbol.
- RetEffect Ret;
-
-public:
- RetainSummary(ArgEffects A, RetEffect R, ArgEffect defaultEff,
- ArgEffect ReceiverEff)
- : Args(A), DefaultArgEffect(defaultEff), Receiver(ReceiverEff), Ret(R) {}
-
- /// getArg - Return the argument effect on the argument specified by
- /// idx (starting from 0).
- ArgEffect getArg(unsigned idx) const {
- if (const ArgEffect *AE = Args.lookup(idx))
- return *AE;
-
- return DefaultArgEffect;
- }
-
- void addArg(ArgEffects::Factory &af, unsigned idx, ArgEffect e) {
- Args = af.add(Args, idx, e);
- }
-
- /// setDefaultArgEffect - Set the default argument effect.
- void setDefaultArgEffect(ArgEffect E) {
- DefaultArgEffect = E;
- }
-
- /// getRetEffect - Returns the effect on the return value of the call.
- RetEffect getRetEffect() const { return Ret; }
-
- /// setRetEffect - Set the effect of the return value of the call.
- void setRetEffect(RetEffect E) { Ret = E; }
-
-
- /// Sets the effect on the receiver of the message.
- void setReceiverEffect(ArgEffect e) { Receiver = e; }
-
- /// getReceiverEffect - Returns the effect on the receiver of the call.
- /// This is only meaningful if the summary applies to an ObjCMessageExpr*.
- ArgEffect getReceiverEffect() const { return Receiver; }
-
- /// Test if two retain summaries are identical. Note that merely equivalent
- /// summaries are not necessarily identical (for example, if an explicit
- /// argument effect matches the default effect).
- bool operator==(const RetainSummary &Other) const {
- return Args == Other.Args && DefaultArgEffect == Other.DefaultArgEffect &&
- Receiver == Other.Receiver && Ret == Other.Ret;
- }
-
- /// Profile this summary for inclusion in a FoldingSet.
- void Profile(llvm::FoldingSetNodeID& ID) const {
- ID.Add(Args);
- ID.Add(DefaultArgEffect);
- ID.Add(Receiver);
- ID.Add(Ret);
- }
-
- /// A retain summary is simple if it has no ArgEffects other than the default.
- bool isSimple() const {
- return Args.isEmpty();
- }
-
-private:
- ArgEffects getArgEffects() const { return Args; }
- ArgEffect getDefaultArgEffect() const { return DefaultArgEffect; }
-
- friend class RetainSummaryManager;
- friend class RetainCountChecker;
-};
-} // end anonymous namespace
-
-//===----------------------------------------------------------------------===//
-// Data structures for constructing summaries.
-//===----------------------------------------------------------------------===//
-
-namespace {
-class ObjCSummaryKey {
- IdentifierInfo* II;
- Selector S;
-public:
- ObjCSummaryKey(IdentifierInfo* ii, Selector s)
- : II(ii), S(s) {}
-
- ObjCSummaryKey(const ObjCInterfaceDecl *d, Selector s)
- : II(d ? d->getIdentifier() : nullptr), S(s) {}
-
- ObjCSummaryKey(Selector s)
- : II(nullptr), S(s) {}
-
- IdentifierInfo *getIdentifier() const { return II; }
- Selector getSelector() const { return S; }
-};
-} // end anonymous namespace
-
-namespace llvm {
-template <> struct DenseMapInfo<ObjCSummaryKey> {
- static inline ObjCSummaryKey getEmptyKey() {
- return ObjCSummaryKey(DenseMapInfo<IdentifierInfo*>::getEmptyKey(),
- DenseMapInfo<Selector>::getEmptyKey());
- }
-
- static inline ObjCSummaryKey getTombstoneKey() {
- return ObjCSummaryKey(DenseMapInfo<IdentifierInfo*>::getTombstoneKey(),
- DenseMapInfo<Selector>::getTombstoneKey());
- }
-
- static unsigned getHashValue(const ObjCSummaryKey &V) {
- typedef std::pair<IdentifierInfo*, Selector> PairTy;
- return DenseMapInfo<PairTy>::getHashValue(PairTy(V.getIdentifier(),
- V.getSelector()));
- }
-
- static bool isEqual(const ObjCSummaryKey& LHS, const ObjCSummaryKey& RHS) {
- return LHS.getIdentifier() == RHS.getIdentifier() &&
- LHS.getSelector() == RHS.getSelector();
- }
-
-};
-} // end llvm namespace
-
-namespace {
-class ObjCSummaryCache {
- typedef llvm::DenseMap<ObjCSummaryKey, const RetainSummary *> MapTy;
- MapTy M;
-public:
- ObjCSummaryCache() {}
-
- const RetainSummary * find(const ObjCInterfaceDecl *D, Selector S) {
- // Do a lookup with the (D,S) pair. If we find a match return
- // the iterator.
- ObjCSummaryKey K(D, S);
- MapTy::iterator I = M.find(K);
-
- if (I != M.end())
- return I->second;
- if (!D)
- return nullptr;
-
- // Walk the super chain. If we find a hit with a parent, we'll end
- // up returning that summary. We actually allow that key (null,S), as
- // we cache summaries for the null ObjCInterfaceDecl* to allow us to
- // generate initial summaries without having to worry about NSObject
- // being declared.
- // FIXME: We may change this at some point.
- for (ObjCInterfaceDecl *C=D->getSuperClass() ;; C=C->getSuperClass()) {
- if ((I = M.find(ObjCSummaryKey(C, S))) != M.end())
- break;
-
- if (!C)
- return nullptr;
- }
-
- // Cache the summary with original key to make the next lookup faster
- // and return the iterator.
- const RetainSummary *Summ = I->second;
- M[K] = Summ;
- return Summ;
- }
-
- const RetainSummary *find(IdentifierInfo* II, Selector S) {
- // FIXME: Class method lookup. Right now we don't have a good way
- // of going between IdentifierInfo* and the class hierarchy.
- MapTy::iterator I = M.find(ObjCSummaryKey(II, S));
-
- if (I == M.end())
- I = M.find(ObjCSummaryKey(S));
-
- return I == M.end() ? nullptr : I->second;
- }
-
- const RetainSummary *& operator[](ObjCSummaryKey K) {
- return M[K];
- }
-
- const RetainSummary *& operator[](Selector S) {
- return M[ ObjCSummaryKey(S) ];
- }
-};
-} // end anonymous namespace
-
-//===----------------------------------------------------------------------===//
-// Data structures for managing collections of summaries.
-//===----------------------------------------------------------------------===//
-
-namespace {
-class RetainSummaryManager {
-
- //==-----------------------------------------------------------------==//
- // Typedefs.
- //==-----------------------------------------------------------------==//
-
- typedef llvm::DenseMap<const FunctionDecl*, const RetainSummary *>
- FuncSummariesTy;
-
- typedef ObjCSummaryCache ObjCMethodSummariesTy;
-
- typedef llvm::FoldingSetNodeWrapper<RetainSummary> CachedSummaryNode;
-
- //==-----------------------------------------------------------------==//
- // Data.
- //==-----------------------------------------------------------------==//
-
- /// Ctx - The ASTContext object for the analyzed ASTs.
- ASTContext &Ctx;
-
- /// GCEnabled - Records whether or not the analyzed code runs in GC mode.
- const bool GCEnabled;
-
- /// Records whether or not the analyzed code runs in ARC mode.
- const bool ARCEnabled;
-
- /// FuncSummaries - A map from FunctionDecls to summaries.
- FuncSummariesTy FuncSummaries;
-
- /// ObjCClassMethodSummaries - A map from selectors (for instance methods)
- /// to summaries.
- ObjCMethodSummariesTy ObjCClassMethodSummaries;
-
- /// ObjCMethodSummaries - A map from selectors to summaries.
- ObjCMethodSummariesTy ObjCMethodSummaries;
-
- /// BPAlloc - A BumpPtrAllocator used for allocating summaries, ArgEffects,
- /// and all other data used by the checker.
- llvm::BumpPtrAllocator BPAlloc;
-
- /// AF - A factory for ArgEffects objects.
- ArgEffects::Factory AF;
-
- /// ScratchArgs - A holding buffer for construct ArgEffects.
- ArgEffects ScratchArgs;
-
- /// ObjCAllocRetE - Default return effect for methods returning Objective-C
- /// objects.
- RetEffect ObjCAllocRetE;
-
- /// ObjCInitRetE - Default return effect for init methods returning
- /// Objective-C objects.
- RetEffect ObjCInitRetE;
-
- /// SimpleSummaries - Used for uniquing summaries that don't have special
- /// effects.
- llvm::FoldingSet<CachedSummaryNode> SimpleSummaries;
-
- //==-----------------------------------------------------------------==//
- // Methods.
- //==-----------------------------------------------------------------==//
-
- /// getArgEffects - Returns a persistent ArgEffects object based on the
- /// data in ScratchArgs.
- ArgEffects getArgEffects();
-
- enum UnaryFuncKind { cfretain, cfrelease, cfautorelease, cfmakecollectable };
-
- const RetainSummary *getUnarySummary(const FunctionType* FT,
- UnaryFuncKind func);
-
- const RetainSummary *getCFSummaryCreateRule(const FunctionDecl *FD);
- const RetainSummary *getCFSummaryGetRule(const FunctionDecl *FD);
- const RetainSummary *getCFCreateGetRuleSummary(const FunctionDecl *FD);
-
- const RetainSummary *getPersistentSummary(const RetainSummary &OldSumm);
-
- const RetainSummary *getPersistentSummary(RetEffect RetEff,
- ArgEffect ReceiverEff = DoNothing,
- ArgEffect DefaultEff = MayEscape) {
- RetainSummary Summ(getArgEffects(), RetEff, DefaultEff, ReceiverEff);
- return getPersistentSummary(Summ);
- }
-
- const RetainSummary *getDoNothingSummary() {
- return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- }
-
- const RetainSummary *getDefaultSummary() {
- return getPersistentSummary(RetEffect::MakeNoRet(),
- DoNothing, MayEscape);
- }
-
- const RetainSummary *getPersistentStopSummary() {
- return getPersistentSummary(RetEffect::MakeNoRet(),
- StopTracking, StopTracking);
- }
-
- void InitializeClassMethodSummaries();
- void InitializeMethodSummaries();
-private:
- void addNSObjectClsMethSummary(Selector S, const RetainSummary *Summ) {
- ObjCClassMethodSummaries[S] = Summ;
- }
-
- void addNSObjectMethSummary(Selector S, const RetainSummary *Summ) {
- ObjCMethodSummaries[S] = Summ;
- }
-
- void addClassMethSummary(const char* Cls, const char* name,
- const RetainSummary *Summ, bool isNullary = true) {
- IdentifierInfo* ClsII = &Ctx.Idents.get(Cls);
- Selector S = isNullary ? GetNullarySelector(name, Ctx)
- : GetUnarySelector(name, Ctx);
- ObjCClassMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ;
- }
-
- void addInstMethSummary(const char* Cls, const char* nullaryName,
- const RetainSummary *Summ) {
- IdentifierInfo* ClsII = &Ctx.Idents.get(Cls);
- Selector S = GetNullarySelector(nullaryName, Ctx);
- ObjCMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ;
- }
-
- template <typename... Keywords>
- void addMethodSummary(IdentifierInfo *ClsII, ObjCMethodSummariesTy &Summaries,
- const RetainSummary *Summ, Keywords *... Kws) {
- Selector S = getKeywordSelector(Ctx, Kws...);
- Summaries[ObjCSummaryKey(ClsII, S)] = Summ;
- }
-
- template <typename... Keywords>
- void addInstMethSummary(const char *Cls, const RetainSummary *Summ,
- Keywords *... Kws) {
- addMethodSummary(&Ctx.Idents.get(Cls), ObjCMethodSummaries, Summ, Kws...);
- }
-
- template <typename... Keywords>
- void addClsMethSummary(const char *Cls, const RetainSummary *Summ,
- Keywords *... Kws) {
- addMethodSummary(&Ctx.Idents.get(Cls), ObjCClassMethodSummaries, Summ,
- Kws...);
- }
-
- template <typename... Keywords>
- void addClsMethSummary(IdentifierInfo *II, const RetainSummary *Summ,
- Keywords *... Kws) {
- addMethodSummary(II, ObjCClassMethodSummaries, Summ, Kws...);
- }
-
-public:
-
- RetainSummaryManager(ASTContext &ctx, bool gcenabled, bool usesARC)
- : Ctx(ctx),
- GCEnabled(gcenabled),
- ARCEnabled(usesARC),
- AF(BPAlloc), ScratchArgs(AF.getEmptyMap()),
- ObjCAllocRetE(gcenabled
- ? RetEffect::MakeGCNotOwned()
- : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC)
- : RetEffect::MakeOwned(RetEffect::ObjC))),
- ObjCInitRetE(gcenabled
- ? RetEffect::MakeGCNotOwned()
- : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC)
- : RetEffect::MakeOwnedWhenTrackedReceiver())) {
- InitializeClassMethodSummaries();
- InitializeMethodSummaries();
- }
-
- const RetainSummary *getSummary(const CallEvent &Call,
- ProgramStateRef State = nullptr);
-
- const RetainSummary *getFunctionSummary(const FunctionDecl *FD);
-
- const RetainSummary *getMethodSummary(Selector S, const ObjCInterfaceDecl *ID,
- const ObjCMethodDecl *MD,
- QualType RetTy,
- ObjCMethodSummariesTy &CachedSummaries);
-
- const RetainSummary *getInstanceMethodSummary(const ObjCMethodCall &M,
- ProgramStateRef State);
-
- const RetainSummary *getClassMethodSummary(const ObjCMethodCall &M) {
- assert(!M.isInstanceMessage());
- const ObjCInterfaceDecl *Class = M.getReceiverInterface();
-
- return getMethodSummary(M.getSelector(), Class, M.getDecl(),
- M.getResultType(), ObjCClassMethodSummaries);
- }
-
- /// getMethodSummary - This version of getMethodSummary is used to query
- /// the summary for the current method being analyzed.
- const RetainSummary *getMethodSummary(const ObjCMethodDecl *MD) {
- const ObjCInterfaceDecl *ID = MD->getClassInterface();
- Selector S = MD->getSelector();
- QualType ResultTy = MD->getReturnType();
-
- ObjCMethodSummariesTy *CachedSummaries;
- if (MD->isInstanceMethod())
- CachedSummaries = &ObjCMethodSummaries;
- else
- CachedSummaries = &ObjCClassMethodSummaries;
-
- return getMethodSummary(S, ID, MD, ResultTy, *CachedSummaries);
- }
-
- const RetainSummary *getStandardMethodSummary(const ObjCMethodDecl *MD,
- Selector S, QualType RetTy);
-
- /// Determine if there is a special return effect for this function or method.
- Optional<RetEffect> getRetEffectFromAnnotations(QualType RetTy,
- const Decl *D);
-
- void updateSummaryFromAnnotations(const RetainSummary *&Summ,
- const ObjCMethodDecl *MD);
-
- void updateSummaryFromAnnotations(const RetainSummary *&Summ,
- const FunctionDecl *FD);
-
- void updateSummaryForCall(const RetainSummary *&Summ,
- const CallEvent &Call);
-
- bool isGCEnabled() const { return GCEnabled; }
-
- bool isARCEnabled() const { return ARCEnabled; }
-
- bool isARCorGCEnabled() const { return GCEnabled || ARCEnabled; }
-
- RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; }
-
- friend class RetainSummaryTemplate;
-};
-
-// Used to avoid allocating long-term (BPAlloc'd) memory for default retain
-// summaries. If a function or method looks like it has a default summary, but
-// it has annotations, the annotations are added to the stack-based template
-// and then copied into managed memory.
-class RetainSummaryTemplate {
- RetainSummaryManager &Manager;
- const RetainSummary *&RealSummary;
- RetainSummary ScratchSummary;
- bool Accessed;
-public:
- RetainSummaryTemplate(const RetainSummary *&real, RetainSummaryManager &mgr)
- : Manager(mgr), RealSummary(real), ScratchSummary(*real), Accessed(false) {}
-
- ~RetainSummaryTemplate() {
- if (Accessed)
- RealSummary = Manager.getPersistentSummary(ScratchSummary);
- }
-
- RetainSummary &operator*() {
- Accessed = true;
- return ScratchSummary;
- }
-
- RetainSummary *operator->() {
- Accessed = true;
- return &ScratchSummary;
- }
-};
-
-} // end anonymous namespace
-
-//===----------------------------------------------------------------------===//
-// Implementation of checker data structures.
-//===----------------------------------------------------------------------===//
-
-ArgEffects RetainSummaryManager::getArgEffects() {
- ArgEffects AE = ScratchArgs;
- ScratchArgs = AF.getEmptyMap();
- return AE;
-}
-
-const RetainSummary *
-RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) {
- // Unique "simple" summaries -- those without ArgEffects.
- if (OldSumm.isSimple()) {
- llvm::FoldingSetNodeID ID;
- OldSumm.Profile(ID);
-
- void *Pos;
- CachedSummaryNode *N = SimpleSummaries.FindNodeOrInsertPos(ID, Pos);
-
- if (!N) {
- N = (CachedSummaryNode *) BPAlloc.Allocate<CachedSummaryNode>();
- new (N) CachedSummaryNode(OldSumm);
- SimpleSummaries.InsertNode(N, Pos);
- }
-
- return &N->getValue();
- }
-
- RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>();
- new (Summ) RetainSummary(OldSumm);
- return Summ;
-}
-
-//===----------------------------------------------------------------------===//
-// Summary creation for functions (largely uses of Core Foundation).
-//===----------------------------------------------------------------------===//
-
-static bool isRetain(const FunctionDecl *FD, StringRef FName) {
- return FName.startswith_lower("retain") || FName.endswith_lower("retain");
-}
-
-static bool isRelease(const FunctionDecl *FD, StringRef FName) {
- return FName.startswith_lower("release") || FName.endswith_lower("release");
-}
-
-static bool isAutorelease(const FunctionDecl *FD, StringRef FName) {
- return FName.startswith_lower("autorelease") ||
- FName.endswith_lower("autorelease");
-}
-
-static bool isMakeCollectable(const FunctionDecl *FD, StringRef FName) {
- // FIXME: Remove FunctionDecl parameter.
- // FIXME: Is it really okay if MakeCollectable isn't a suffix?
- return FName.find_lower("MakeCollectable") != StringRef::npos;
-}
-
-static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) {
- switch (E) {
- case DoNothing:
- case Autorelease:
- case DecRefBridgedTransferred:
- case IncRef:
- case IncRefMsg:
- case MakeCollectable:
- case UnretainedOutParameter:
- case RetainedOutParameter:
- case MayEscape:
- case StopTracking:
- case StopTrackingHard:
- return StopTrackingHard;
- case DecRef:
- case DecRefAndStopTrackingHard:
- return DecRefAndStopTrackingHard;
- case DecRefMsg:
- case DecRefMsgAndStopTrackingHard:
- return DecRefMsgAndStopTrackingHard;
- case Dealloc:
- return Dealloc;
- }
-
- llvm_unreachable("Unknown ArgEffect kind");
-}
-
-void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S,
- const CallEvent &Call) {
- if (Call.hasNonZeroCallbackArg()) {
- ArgEffect RecEffect =
- getStopTrackingHardEquivalent(S->getReceiverEffect());
- ArgEffect DefEffect =
- getStopTrackingHardEquivalent(S->getDefaultArgEffect());
-
- ArgEffects CustomArgEffects = S->getArgEffects();
- for (ArgEffects::iterator I = CustomArgEffects.begin(),
- E = CustomArgEffects.end();
- I != E; ++I) {
- ArgEffect Translated = getStopTrackingHardEquivalent(I->second);
- if (Translated != DefEffect)
- ScratchArgs = AF.add(ScratchArgs, I->first, Translated);
- }
-
- RetEffect RE = RetEffect::MakeNoRetHard();
-
- // Special cases where the callback argument CANNOT free the return value.
- // This can generally only happen if we know that the callback will only be
- // called when the return value is already being deallocated.
- if (const SimpleFunctionCall *FC = dyn_cast<SimpleFunctionCall>(&Call)) {
- if (IdentifierInfo *Name = FC->getDecl()->getIdentifier()) {
- // When the CGBitmapContext is deallocated, the callback here will free
- // the associated data buffer.
- // The callback in dispatch_data_create frees the buffer, but not
- // the data object.
- if (Name->isStr("CGBitmapContextCreateWithData") ||
- Name->isStr("dispatch_data_create"))
- RE = S->getRetEffect();
- }
- }
-
- S = getPersistentSummary(RE, RecEffect, DefEffect);
- }
-
- // Special case '[super init];' and '[self init];'
- //
- // Even though calling '[super init]' without assigning the result to self
- // and checking if the parent returns 'nil' is a bad pattern, it is common.
- // Additionally, our Self Init checker already warns about it. To avoid
- // overwhelming the user with messages from both checkers, we model the case
- // of '[super init]' in cases when it is not consumed by another expression
- // as if the call preserves the value of 'self'; essentially, assuming it can
- // never fail and return 'nil'.
- // Note, we don't want to just stop tracking the value since we want the
- // RetainCount checker to report leaks and use-after-free if SelfInit checker
- // is turned off.
- if (const ObjCMethodCall *MC = dyn_cast<ObjCMethodCall>(&Call)) {
- if (MC->getMethodFamily() == OMF_init && MC->isReceiverSelfOrSuper()) {
-
- // Check if the message is not consumed, we know it will not be used in
- // an assignment, ex: "self = [super init]".
- const Expr *ME = MC->getOriginExpr();
- const LocationContext *LCtx = MC->getLocationContext();
- ParentMap &PM = LCtx->getAnalysisDeclContext()->getParentMap();
- if (!PM.isConsumedExpr(ME)) {
- RetainSummaryTemplate ModifiableSummaryTemplate(S, *this);
- ModifiableSummaryTemplate->setReceiverEffect(DoNothing);
- ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet());
- }
- }
- }
-}
-
-const RetainSummary *
-RetainSummaryManager::getSummary(const CallEvent &Call,
- ProgramStateRef State) {
- const RetainSummary *Summ;
- switch (Call.getKind()) {
- case CE_Function:
- Summ = getFunctionSummary(cast<SimpleFunctionCall>(Call).getDecl());
- break;
- case CE_CXXMember:
- case CE_CXXMemberOperator:
- case CE_Block:
- case CE_CXXConstructor:
- case CE_CXXDestructor:
- case CE_CXXAllocator:
- // FIXME: These calls are currently unsupported.
- return getPersistentStopSummary();
- case CE_ObjCMessage: {
- const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call);
- if (Msg.isInstanceMessage())
- Summ = getInstanceMethodSummary(Msg, State);
- else
- Summ = getClassMethodSummary(Msg);
- break;
- }
- }
-
- updateSummaryForCall(Summ, Call);
-
- assert(Summ && "Unknown call type?");
- return Summ;
-}
-
-const RetainSummary *
-RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) {
- // If we don't know what function we're calling, use our default summary.
- if (!FD)
- return getDefaultSummary();
-
- // Look up a summary in our cache of FunctionDecls -> Summaries.
- FuncSummariesTy::iterator I = FuncSummaries.find(FD);
- if (I != FuncSummaries.end())
- return I->second;
-
- // No summary? Generate one.
- const RetainSummary *S = nullptr;
- bool AllowAnnotations = true;
-
- do {
- // We generate "stop" summaries for implicitly defined functions.
- if (FD->isImplicit()) {
- S = getPersistentStopSummary();
- break;
- }
-
- // [PR 3337] Use 'getAs<FunctionType>' to strip away any typedefs on the
- // function's type.
- const FunctionType* FT = FD->getType()->getAs<FunctionType>();
- const IdentifierInfo *II = FD->getIdentifier();
- if (!II)
- break;
-
- StringRef FName = II->getName();
-
- // Strip away preceding '_'. Doing this here will effect all the checks
- // down below.
- FName = FName.substr(FName.find_first_not_of('_'));
-
- // Inspect the result type.
- QualType RetTy = FT->getReturnType();
- std::string RetTyName = RetTy.getAsString();
-
- // FIXME: This should all be refactored into a chain of "summary lookup"
- // filters.
- assert(ScratchArgs.isEmpty());
-
- if (FName == "pthread_create" || FName == "pthread_setspecific") {
- // Part of: <rdar://problem/7299394> and <rdar://problem/11282706>.
- // This will be addressed better with IPA.
- S = getPersistentStopSummary();
- } else if (FName == "NSMakeCollectable") {
- // Handle: id NSMakeCollectable(CFTypeRef)
- S = (RetTy->isObjCIdType())
- ? getUnarySummary(FT, cfmakecollectable)
- : getPersistentStopSummary();
- // The headers on OS X 10.8 use cf_consumed/ns_returns_retained,
- // but we can fully model NSMakeCollectable ourselves.
- AllowAnnotations = false;
- } else if (FName == "CFPlugInInstanceCreate") {
- S = getPersistentSummary(RetEffect::MakeNoRet());
- } else if (FName == "IORegistryEntrySearchCFProperty"
- || (RetTyName == "CFMutableDictionaryRef" && (
- FName == "IOBSDNameMatching" ||
- FName == "IOServiceMatching" ||
- FName == "IOServiceNameMatching" ||
- FName == "IORegistryEntryIDMatching" ||
- FName == "IOOpenFirmwarePathMatching"
- ))) {
- // Part of <rdar://problem/6961230>. (IOKit)
- // This should be addressed using a API table.
- S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF),
- DoNothing, DoNothing);
- } else if (FName == "IOServiceGetMatchingService" ||
- FName == "IOServiceGetMatchingServices") {
- // FIXES: <rdar://problem/6326900>
- // This should be addressed using a API table. This strcmp is also
- // a little gross, but there is no need to super optimize here.
- ScratchArgs = AF.add(ScratchArgs, 1, DecRef);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName == "IOServiceAddNotification" ||
- FName == "IOServiceAddMatchingNotification") {
- // Part of <rdar://problem/6961230>. (IOKit)
- // This should be addressed using a API table.
- ScratchArgs = AF.add(ScratchArgs, 2, DecRef);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName == "CVPixelBufferCreateWithBytes") {
- // FIXES: <rdar://problem/7283567>
- // Eventually this can be improved by recognizing that the pixel
- // buffer passed to CVPixelBufferCreateWithBytes is released via
- // a callback and doing full IPA to make sure this is done correctly.
- // FIXME: This function has an out parameter that returns an
- // allocated object.
- ScratchArgs = AF.add(ScratchArgs, 7, StopTracking);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName == "CGBitmapContextCreateWithData") {
- // FIXES: <rdar://problem/7358899>
- // Eventually this can be improved by recognizing that 'releaseInfo'
- // passed to CGBitmapContextCreateWithData is released via
- // a callback and doing full IPA to make sure this is done correctly.
- ScratchArgs = AF.add(ScratchArgs, 8, StopTracking);
- S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF),
- DoNothing, DoNothing);
- } else if (FName == "CVPixelBufferCreateWithPlanarBytes") {
- // FIXES: <rdar://problem/7283567>
- // Eventually this can be improved by recognizing that the pixel
- // buffer passed to CVPixelBufferCreateWithPlanarBytes is released
- // via a callback and doing full IPA to make sure this is done
- // correctly.
- ScratchArgs = AF.add(ScratchArgs, 12, StopTracking);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName == "VTCompressionSessionEncodeFrame") {
- // The context argument passed to VTCompressionSessionEncodeFrame()
- // is passed to the callback specified when creating the session
- // (e.g. with VTCompressionSessionCreate()) which can release it.
- // To account for this possibility, conservatively stop tracking
- // the context.
- ScratchArgs = AF.add(ScratchArgs, 5, StopTracking);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName == "dispatch_set_context" ||
- FName == "xpc_connection_set_context") {
- // <rdar://problem/11059275> - The analyzer currently doesn't have
- // a good way to reason about the finalizer function for libdispatch.
- // If we pass a context object that is memory managed, stop tracking it.
- // <rdar://problem/13783514> - Same problem, but for XPC.
- // FIXME: this hack should possibly go away once we can handle
- // libdispatch and XPC finalizers.
- ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- } else if (FName.startswith("NSLog")) {
- S = getDoNothingSummary();
- } else if (FName.startswith("NS") &&
- (FName.find("Insert") != StringRef::npos)) {
- // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
- // be deallocated by NSMapRemove. (radar://11152419)
- ScratchArgs = AF.add(ScratchArgs, 1, StopTracking);
- ScratchArgs = AF.add(ScratchArgs, 2, StopTracking);
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
- }
-
- // Did we get a summary?
- if (S)
- break;
-
- if (RetTy->isPointerType()) {
- // For CoreFoundation ('CF') types.
- if (cocoa::isRefType(RetTy, "CF", FName)) {
- if (isRetain(FD, FName)) {
- S = getUnarySummary(FT, cfretain);
- // CFRetain isn't supposed to be annotated. However, this may as well
- // be a user-made "safe" CFRetain function that is incorrectly
- // annotated as cf_returns_retained due to lack of better options.
- // We want to ignore such annotation.
- AllowAnnotations = false;
- } else if (isAutorelease(FD, FName)) {
- S = getUnarySummary(FT, cfautorelease);
- // The headers use cf_consumed, but we can fully model CFAutorelease
- // ourselves.
- AllowAnnotations = false;
- } else if (isMakeCollectable(FD, FName)) {
- S = getUnarySummary(FT, cfmakecollectable);
- AllowAnnotations = false;
- } else {
- S = getCFCreateGetRuleSummary(FD);
- }
-
- break;
- }
-
- // For CoreGraphics ('CG') and CoreVideo ('CV') types.
- if (cocoa::isRefType(RetTy, "CG", FName) ||
- cocoa::isRefType(RetTy, "CV", FName)) {
- if (isRetain(FD, FName))
- S = getUnarySummary(FT, cfretain);
- else
- S = getCFCreateGetRuleSummary(FD);
-
- break;
- }
-
- // For all other CF-style types, use the Create/Get
- // rule for summaries but don't support Retain functions
- // with framework-specific prefixes.
- if (coreFoundation::isCFObjectRef(RetTy)) {
- S = getCFCreateGetRuleSummary(FD);
- break;
- }
-
- if (FD->hasAttr<CFAuditedTransferAttr>()) {
- S = getCFCreateGetRuleSummary(FD);
- break;
- }
-
- break;
- }
-
- // Check for release functions, the only kind of functions that we care
- // about that don't return a pointer type.
- if (FName.size() >= 2 &&
- FName[0] == 'C' && (FName[1] == 'F' || FName[1] == 'G')) {
- // Test for 'CGCF'.
- FName = FName.substr(FName.startswith("CGCF") ? 4 : 2);
-
- if (isRelease(FD, FName))
- S = getUnarySummary(FT, cfrelease);
- else {
- assert (ScratchArgs.isEmpty());
- // Remaining CoreFoundation and CoreGraphics functions.
- // We use to assume that they all strictly followed the ownership idiom
- // and that ownership cannot be transferred. While this is technically
- // correct, many methods allow a tracked object to escape. For example:
- //
- // CFMutableDictionaryRef x = CFDictionaryCreateMutable(...);
- // CFDictionaryAddValue(y, key, x);
- // CFRelease(x);
- // ... it is okay to use 'x' since 'y' has a reference to it
- //
- // We handle this and similar cases with the follow heuristic. If the
- // function name contains "InsertValue", "SetValue", "AddValue",
- // "AppendValue", or "SetAttribute", then we assume that arguments may
- // "escape." This means that something else holds on to the object,
- // allowing it be used even after its local retain count drops to 0.
- ArgEffect E = (StrInStrNoCase(FName, "InsertValue") != StringRef::npos||
- StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
- StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
- StrInStrNoCase(FName, "AppendValue") != StringRef::npos||
- StrInStrNoCase(FName, "SetAttribute") != StringRef::npos)
- ? MayEscape : DoNothing;
-
- S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, E);
- }
- }
- }
- while (0);
-
- // If we got all the way here without any luck, use a default summary.
- if (!S)
- S = getDefaultSummary();
-
- // Annotations override defaults.
- if (AllowAnnotations)
- updateSummaryFromAnnotations(S, FD);
-
- FuncSummaries[FD] = S;
- return S;
-}
-
-const RetainSummary *
-RetainSummaryManager::getCFCreateGetRuleSummary(const FunctionDecl *FD) {
- if (coreFoundation::followsCreateRule(FD))
- return getCFSummaryCreateRule(FD);
-
- return getCFSummaryGetRule(FD);
-}
-
-const RetainSummary *
-RetainSummaryManager::getUnarySummary(const FunctionType* FT,
- UnaryFuncKind func) {
-
- // Sanity check that this is *really* a unary function. This can
- // happen if people do weird things.
- const FunctionProtoType* FTP = dyn_cast<FunctionProtoType>(FT);
- if (!FTP || FTP->getNumParams() != 1)
- return getPersistentStopSummary();
-
- assert (ScratchArgs.isEmpty());
-
- ArgEffect Effect;
- switch (func) {
- case cfretain: Effect = IncRef; break;
- case cfrelease: Effect = DecRef; break;
- case cfautorelease: Effect = Autorelease; break;
- case cfmakecollectable: Effect = MakeCollectable; break;
- }
-
- ScratchArgs = AF.add(ScratchArgs, 0, Effect);
- return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing);
-}
-
-const RetainSummary *
-RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) {
- assert (ScratchArgs.isEmpty());
-
- return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF));
-}
-
-const RetainSummary *
-RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) {
- assert (ScratchArgs.isEmpty());
- return getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::CF),
- DoNothing, DoNothing);
-}
-
-/// Returns true if the declaration 'D' is annotated with 'rcAnnotation'.
-static bool hasRCAnnotation(const Decl *D, StringRef rcAnnotation) {
- for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) {
- if (Ann->getAnnotation() == rcAnnotation)
- return true;
- }
- return false;
-}
-
-/// Returns true if the function declaration 'FD' contains
-/// 'rc_ownership_trusted_implementation' annotate attribute.
-static bool isTrustedReferenceCountImplementation(const FunctionDecl *FD) {
- return hasRCAnnotation(FD, "rc_ownership_trusted_implementation");
-}
-
-static bool isGeneralizedObjectRef(QualType Ty) {
- if (Ty.getAsString().substr(0, 4) == "isl_")
- return true;
- else
- return false;
-}
-
-//===----------------------------------------------------------------------===//
-// Summary creation for Selectors.
-//===----------------------------------------------------------------------===//
-
-Optional<RetEffect>
-RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
- const Decl *D) {
- if (cocoa::isCocoaObjectRef(RetTy)) {
- if (D->hasAttr<NSReturnsRetainedAttr>())
- return ObjCAllocRetE;
-
- if (D->hasAttr<NSReturnsNotRetainedAttr>() ||
- D->hasAttr<NSReturnsAutoreleasedAttr>())
- return RetEffect::MakeNotOwned(RetEffect::ObjC);
-
- } else if (!RetTy->isPointerType()) {
- return None;
- }
-
- if (D->hasAttr<CFReturnsRetainedAttr>())
- return RetEffect::MakeOwned(RetEffect::CF);
- else if (hasRCAnnotation(D, "rc_ownership_returns_retained"))
- return RetEffect::MakeOwned(RetEffect::Generalized);
-
- if (D->hasAttr<CFReturnsNotRetainedAttr>())
- return RetEffect::MakeNotOwned(RetEffect::CF);
-
- return None;
-}
-
-void
-RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
- const FunctionDecl *FD) {
- if (!FD)
- return;
-
- assert(Summ && "Must have a summary to add annotations to.");
- RetainSummaryTemplate Template(Summ, *this);
-
- // Effects on the parameters.
- unsigned parm_idx = 0;
- for (FunctionDecl::param_const_iterator pi = FD->param_begin(),
- pe = FD->param_end(); pi != pe; ++pi, ++parm_idx) {
- const ParmVarDecl *pd = *pi;
- if (pd->hasAttr<NSConsumedAttr>())
- Template->addArg(AF, parm_idx, DecRefMsg);
- else if (pd->hasAttr<CFConsumedAttr>() ||
- hasRCAnnotation(pd, "rc_ownership_consumed"))
- Template->addArg(AF, parm_idx, DecRef);
- else if (pd->hasAttr<CFReturnsRetainedAttr>() ||
- hasRCAnnotation(pd, "rc_ownership_returns_retained")) {
- QualType PointeeTy = pd->getType()->getPointeeType();
- if (!PointeeTy.isNull())
- if (coreFoundation::isCFObjectRef(PointeeTy))
- Template->addArg(AF, parm_idx, RetainedOutParameter);
- } else if (pd->hasAttr<CFReturnsNotRetainedAttr>()) {
- QualType PointeeTy = pd->getType()->getPointeeType();
- if (!PointeeTy.isNull())
- if (coreFoundation::isCFObjectRef(PointeeTy))
- Template->addArg(AF, parm_idx, UnretainedOutParameter);
- }
- }
-
- QualType RetTy = FD->getReturnType();
- if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD))
- Template->setRetEffect(*RetE);
-}
-
-void
-RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
- const ObjCMethodDecl *MD) {
- if (!MD)
- return;
-
- assert(Summ && "Must have a valid summary to add annotations to");
- RetainSummaryTemplate Template(Summ, *this);
-
- // Effects on the receiver.
- if (MD->hasAttr<NSConsumesSelfAttr>())
- Template->setReceiverEffect(DecRefMsg);
-
- // Effects on the parameters.
- unsigned parm_idx = 0;
- for (ObjCMethodDecl::param_const_iterator
- pi=MD->param_begin(), pe=MD->param_end();
- pi != pe; ++pi, ++parm_idx) {
- const ParmVarDecl *pd = *pi;
- if (pd->hasAttr<NSConsumedAttr>())
- Template->addArg(AF, parm_idx, DecRefMsg);
- else if (pd->hasAttr<CFConsumedAttr>()) {
- Template->addArg(AF, parm_idx, DecRef);
- } else if (pd->hasAttr<CFReturnsRetainedAttr>()) {
- QualType PointeeTy = pd->getType()->getPointeeType();
- if (!PointeeTy.isNull())
- if (coreFoundation::isCFObjectRef(PointeeTy))
- Template->addArg(AF, parm_idx, RetainedOutParameter);
- } else if (pd->hasAttr<CFReturnsNotRetainedAttr>()) {
- QualType PointeeTy = pd->getType()->getPointeeType();
- if (!PointeeTy.isNull())
- if (coreFoundation::isCFObjectRef(PointeeTy))
- Template->addArg(AF, parm_idx, UnretainedOutParameter);
- }
- }
-
- QualType RetTy = MD->getReturnType();
- if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, MD))
- Template->setRetEffect(*RetE);
-}
-
-const RetainSummary *
-RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD,
- Selector S, QualType RetTy) {
- // Any special effects?
- ArgEffect ReceiverEff = DoNothing;
- RetEffect ResultEff = RetEffect::MakeNoRet();
-
- // Check the method family, and apply any default annotations.
- switch (MD ? MD->getMethodFamily() : S.getMethodFamily()) {
- case OMF_None:
- case OMF_initialize:
- case OMF_performSelector:
- // Assume all Objective-C methods follow Cocoa Memory Management rules.
- // FIXME: Does the non-threaded performSelector family really belong here?
- // The selector could be, say, @selector(copy).
- if (cocoa::isCocoaObjectRef(RetTy))
- ResultEff = RetEffect::MakeNotOwned(RetEffect::ObjC);
- else if (coreFoundation::isCFObjectRef(RetTy)) {
- // ObjCMethodDecl currently doesn't consider CF objects as valid return
- // values for alloc, new, copy, or mutableCopy, so we have to
- // double-check with the selector. This is ugly, but there aren't that
- // many Objective-C methods that return CF objects, right?
- if (MD) {
- switch (S.getMethodFamily()) {
- case OMF_alloc:
- case OMF_new:
- case OMF_copy:
- case OMF_mutableCopy:
- ResultEff = RetEffect::MakeOwned(RetEffect::CF);
- break;
- default:
- ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
- break;
- }
- } else {
- ResultEff = RetEffect::MakeNotOwned(RetEffect::CF);
- }
- }
- break;
- case OMF_init:
- ResultEff = ObjCInitRetE;
- ReceiverEff = DecRefMsg;
- break;
- case OMF_alloc:
- case OMF_new:
- case OMF_copy:
- case OMF_mutableCopy:
- if (cocoa::isCocoaObjectRef(RetTy))
- ResultEff = ObjCAllocRetE;
- else if (coreFoundation::isCFObjectRef(RetTy))
- ResultEff = RetEffect::MakeOwned(RetEffect::CF);
- break;
- case OMF_autorelease:
- ReceiverEff = Autorelease;
- break;
- case OMF_retain:
- ReceiverEff = IncRefMsg;
- break;
- case OMF_release:
- ReceiverEff = DecRefMsg;
- break;
- case OMF_dealloc:
- ReceiverEff = Dealloc;
- break;
- case OMF_self:
- // -self is handled specially by the ExprEngine to propagate the receiver.
- break;
- case OMF_retainCount:
- case OMF_finalize:
- // These methods don't return objects.
- break;
- }
-
- // If one of the arguments in the selector has the keyword 'delegate' we
- // should stop tracking the reference count for the receiver. This is
- // because the reference count is quite possibly handled by a delegate
- // method.
- if (S.isKeywordSelector()) {
- for (unsigned i = 0, e = S.getNumArgs(); i != e; ++i) {
- StringRef Slot = S.getNameForSlot(i);
- if (Slot.substr(Slot.size() - 8).equals_lower("delegate")) {
- if (ResultEff == ObjCInitRetE)
- ResultEff = RetEffect::MakeNoRetHard();
- else
- ReceiverEff = StopTrackingHard;
- }
- }
- }
-
- if (ScratchArgs.isEmpty() && ReceiverEff == DoNothing &&
- ResultEff.getKind() == RetEffect::NoRet)
- return getDefaultSummary();
-
- return getPersistentSummary(ResultEff, ReceiverEff, MayEscape);
-}
-
-const RetainSummary *
-RetainSummaryManager::getInstanceMethodSummary(const ObjCMethodCall &Msg,
- ProgramStateRef State) {
- const ObjCInterfaceDecl *ReceiverClass = nullptr;
-
- // We do better tracking of the type of the object than the core ExprEngine.
- // See if we have its type in our private state.
- // FIXME: Eventually replace the use of state->get<RefBindings> with
- // a generic API for reasoning about the Objective-C types of symbolic
- // objects.
- SVal ReceiverV = Msg.getReceiverSVal();
- if (SymbolRef Sym = ReceiverV.getAsLocSymbol())
- if (const RefVal *T = getRefBinding(State, Sym))
- if (const ObjCObjectPointerType *PT =
- T->getType()->getAs<ObjCObjectPointerType>())
- ReceiverClass = PT->getInterfaceDecl();
-
- // If we don't know what kind of object this is, fall back to its static type.
- if (!ReceiverClass)
- ReceiverClass = Msg.getReceiverInterface();
-
- // FIXME: The receiver could be a reference to a class, meaning that
- // we should use the class method.
- // id x = [NSObject class];
- // [x performSelector:... withObject:... afterDelay:...];
- Selector S = Msg.getSelector();
- const ObjCMethodDecl *Method = Msg.getDecl();
- if (!Method && ReceiverClass)
- Method = ReceiverClass->getInstanceMethod(S);
-
- return getMethodSummary(S, ReceiverClass, Method, Msg.getResultType(),
- ObjCMethodSummaries);
-}
-
-const RetainSummary *
-RetainSummaryManager::getMethodSummary(Selector S, const ObjCInterfaceDecl *ID,
- const ObjCMethodDecl *MD, QualType RetTy,
- ObjCMethodSummariesTy &CachedSummaries) {
-
- // Look up a summary in our summary cache.
- const RetainSummary *Summ = CachedSummaries.find(ID, S);
-
- if (!Summ) {
- Summ = getStandardMethodSummary(MD, S, RetTy);
-
- // Annotations override defaults.
- updateSummaryFromAnnotations(Summ, MD);
-
- // Memoize the summary.
- CachedSummaries[ObjCSummaryKey(ID, S)] = Summ;
- }
-
- return Summ;
-}
-
-void RetainSummaryManager::InitializeClassMethodSummaries() {
- assert(ScratchArgs.isEmpty());
- // Create the [NSAssertionHandler currentHander] summary.
- addClassMethSummary("NSAssertionHandler", "currentHandler",
- getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::ObjC)));
-
- // Create the [NSAutoreleasePool addObject:] summary.
- ScratchArgs = AF.add(ScratchArgs, 0, Autorelease);
- addClassMethSummary("NSAutoreleasePool", "addObject",
- getPersistentSummary(RetEffect::MakeNoRet(),
- DoNothing, Autorelease));
-}
-
-void RetainSummaryManager::InitializeMethodSummaries() {
-
- assert (ScratchArgs.isEmpty());
-
- // Create the "init" selector. It just acts as a pass-through for the
- // receiver.
- const RetainSummary *InitSumm = getPersistentSummary(ObjCInitRetE, DecRefMsg);
- addNSObjectMethSummary(GetNullarySelector("init", Ctx), InitSumm);
-
- // awakeAfterUsingCoder: behaves basically like an 'init' method. It
- // claims the receiver and returns a retained object.
- addNSObjectMethSummary(GetUnarySelector("awakeAfterUsingCoder", Ctx),
- InitSumm);
-
- // The next methods are allocators.
- const RetainSummary *AllocSumm = getPersistentSummary(ObjCAllocRetE);
- const RetainSummary *CFAllocSumm =
- getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF));
-
- // Create the "retain" selector.
- RetEffect NoRet = RetEffect::MakeNoRet();
- const RetainSummary *Summ = getPersistentSummary(NoRet, IncRefMsg);
- addNSObjectMethSummary(GetNullarySelector("retain", Ctx), Summ);
-
- // Create the "release" selector.
- Summ = getPersistentSummary(NoRet, DecRefMsg);
- addNSObjectMethSummary(GetNullarySelector("release", Ctx), Summ);
-
- // Create the -dealloc summary.
- Summ = getPersistentSummary(NoRet, Dealloc);
- addNSObjectMethSummary(GetNullarySelector("dealloc", Ctx), Summ);
-
- // Create the "autorelease" selector.
- Summ = getPersistentSummary(NoRet, Autorelease);
- addNSObjectMethSummary(GetNullarySelector("autorelease", Ctx), Summ);
-
- // For NSWindow, allocated objects are (initially) self-owned.
- // FIXME: For now we opt for false negatives with NSWindow, as these objects
- // self-own themselves. However, they only do this once they are displayed.
- // Thus, we need to track an NSWindow's display status.
- // This is tracked in <rdar://problem/6062711>.
- // See also http://llvm.org/bugs/show_bug.cgi?id=3714.
- const RetainSummary *NoTrackYet = getPersistentSummary(RetEffect::MakeNoRet(),
- StopTracking,
- StopTracking);
-
- addClassMethSummary("NSWindow", "alloc", NoTrackYet);
-
- // For NSPanel (which subclasses NSWindow), allocated objects are not
- // self-owned.
- // FIXME: For now we don't track NSPanels. object for the same reason
- // as for NSWindow objects.
- addClassMethSummary("NSPanel", "alloc", NoTrackYet);
-
- // For NSNull, objects returned by +null are singletons that ignore
- // retain/release semantics. Just don't track them.
- // <rdar://problem/12858915>
- addClassMethSummary("NSNull", "null", NoTrackYet);
-
- // Don't track allocated autorelease pools, as it is okay to prematurely
- // exit a method.
- addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet);
- addClassMethSummary("NSAutoreleasePool", "allocWithZone", NoTrackYet, false);
- addClassMethSummary("NSAutoreleasePool", "new", NoTrackYet);
-
- // Create summaries QCRenderer/QCView -createSnapShotImageOfType:
- addInstMethSummary("QCRenderer", AllocSumm, "createSnapshotImageOfType");
- addInstMethSummary("QCView", AllocSumm, "createSnapshotImageOfType");
-
- // Create summaries for CIContext, 'createCGImage' and
- // 'createCGLayerWithSize'. These objects are CF objects, and are not
- // automatically garbage collected.
- addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect");
- addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect",
- "format", "colorSpace");
- addInstMethSummary("CIContext", CFAllocSumm, "createCGLayerWithSize", "info");
-}
-
-//===----------------------------------------------------------------------===//
-// Error reporting.
-//===----------------------------------------------------------------------===//
-namespace {
- typedef llvm::DenseMap<const ExplodedNode *, const RetainSummary *>
- SummaryLogTy;
-
- //===-------------===//
- // Bug Descriptions. //
- //===-------------===//
-
- class CFRefBug : public BugType {
- protected:
- CFRefBug(const CheckerBase *checker, StringRef name)
- : BugType(checker, name, categories::MemoryCoreFoundationObjectiveC) {}
-
- public:
-
- // FIXME: Eventually remove.
- virtual const char *getDescription() const = 0;
-
- virtual bool isLeak() const { return false; }
- };
-
- class UseAfterRelease : public CFRefBug {
- public:
- UseAfterRelease(const CheckerBase *checker)
- : CFRefBug(checker, "Use-after-release") {}
-
- const char *getDescription() const override {
- return "Reference-counted object is used after it is released";
- }
- };
-
- class BadRelease : public CFRefBug {
- public:
- BadRelease(const CheckerBase *checker) : CFRefBug(checker, "Bad release") {}
-
- const char *getDescription() const override {
- return "Incorrect decrement of the reference count of an object that is "
- "not owned at this point by the caller";
- }
- };
-
- class DeallocGC : public CFRefBug {
- public:
- DeallocGC(const CheckerBase *checker)
- : CFRefBug(checker, "-dealloc called while using garbage collection") {}
-
- const char *getDescription() const override {
- return "-dealloc called while using garbage collection";
- }
- };
-
- class DeallocNotOwned : public CFRefBug {
- public:
- DeallocNotOwned(const CheckerBase *checker)
- : CFRefBug(checker, "-dealloc sent to non-exclusively owned object") {}
-
- const char *getDescription() const override {
- return "-dealloc sent to object that may be referenced elsewhere";
- }
- };
-
- class OverAutorelease : public CFRefBug {
- public:
- OverAutorelease(const CheckerBase *checker)
- : CFRefBug(checker, "Object autoreleased too many times") {}
-
- const char *getDescription() const override {
- return "Object autoreleased too many times";
- }
- };
-
- class ReturnedNotOwnedForOwned : public CFRefBug {
- public:
- ReturnedNotOwnedForOwned(const CheckerBase *checker)
- : CFRefBug(checker, "Method should return an owned object") {}
-
- const char *getDescription() const override {
- return "Object with a +0 retain count returned to caller where a +1 "
- "(owning) retain count is expected";
- }
- };
-
- class Leak : public CFRefBug {
- public:
- Leak(const CheckerBase *checker, StringRef name) : CFRefBug(checker, name) {
- // Leaks should not be reported if they are post-dominated by a sink.
- setSuppressOnSink(true);
- }
-
- const char *getDescription() const override { return ""; }
-
- bool isLeak() const override { return true; }
- };
-
- //===---------===//
- // Bug Reports. //
- //===---------===//
- class CFRefReportVisitor : public BugReporterVisitor {
- protected:
- SymbolRef Sym;
- const SummaryLogTy &SummaryLog;
- bool GCEnabled;
-
- public:
- CFRefReportVisitor(SymbolRef sym, bool gcEnabled, const SummaryLogTy &log)
- : Sym(sym), SummaryLog(log), GCEnabled(gcEnabled) {}
-
- void Profile(llvm::FoldingSetNodeID &ID) const override {
- static int x = 0;
- ID.AddPointer(&x);
- ID.AddPointer(Sym);
- }
-
- std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC,
- BugReport &BR) override;
-
- std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC,
- const ExplodedNode *N,
- BugReport &BR) override;
- };
-
- class CFRefLeakReportVisitor : public CFRefReportVisitor {
- public:
- CFRefLeakReportVisitor(SymbolRef sym, bool GCEnabled,
- const SummaryLogTy &log)
- : CFRefReportVisitor(sym, GCEnabled, log) {}
-
- std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC,
- const ExplodedNode *N,
- BugReport &BR) override;
- };
-
- class CFRefReport : public BugReport {
- void addGCModeDescription(const LangOptions &LOpts, bool GCEnabled);
-
- public:
- CFRefReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled,
- const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym,
- bool registerVisitor = true)
- : BugReport(D, D.getDescription(), n) {
- if (registerVisitor)
- addVisitor(llvm::make_unique<CFRefReportVisitor>(sym, GCEnabled, Log));
- addGCModeDescription(LOpts, GCEnabled);
- }
-
- CFRefReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled,
- const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym,
- StringRef endText)
- : BugReport(D, D.getDescription(), endText, n) {
- addVisitor(llvm::make_unique<CFRefReportVisitor>(sym, GCEnabled, Log));
- addGCModeDescription(LOpts, GCEnabled);
- }
-
- llvm::iterator_range<ranges_iterator> getRanges() override {
- const CFRefBug& BugTy = static_cast<CFRefBug&>(getBugType());
- if (!BugTy.isLeak())
- return BugReport::getRanges();
- return llvm::make_range(ranges_iterator(), ranges_iterator());
- }
- };
-
- class CFRefLeakReport : public CFRefReport {
- const MemRegion* AllocBinding;
- const Stmt *AllocStmt;
-
- // Finds the function declaration where a leak warning for the parameter 'sym' should be raised.
- void deriveParamLocation(CheckerContext &Ctx, SymbolRef sym);
- // Finds the location where a leak warning for 'sym' should be raised.
- void deriveAllocLocation(CheckerContext &Ctx, SymbolRef sym);
- // Produces description of a leak warning which is printed on the console.
- void createDescription(CheckerContext &Ctx, bool GCEnabled, bool IncludeAllocationLine);
-
- public:
- CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled,
- const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym,
- CheckerContext &Ctx,
- bool IncludeAllocationLine);
-
- PathDiagnosticLocation getLocation(const SourceManager &SM) const override {
- assert(Location.isValid());
- return Location;
- }
- };
-} // end anonymous namespace
-
-void CFRefReport::addGCModeDescription(const LangOptions &LOpts,
- bool GCEnabled) {
- const char *GCModeDescription = nullptr;
-
- switch (LOpts.getGC()) {
- case LangOptions::GCOnly:
- assert(GCEnabled);
- GCModeDescription = "Code is compiled to only use garbage collection";
- break;
-
- case LangOptions::NonGC:
- assert(!GCEnabled);
- GCModeDescription = "Code is compiled to use reference counts";
- break;
-
- case LangOptions::HybridGC:
- if (GCEnabled) {
- GCModeDescription = "Code is compiled to use either garbage collection "
- "(GC) or reference counts (non-GC). The bug occurs "
- "with GC enabled";
- break;
- } else {
- GCModeDescription = "Code is compiled to use either garbage collection "
- "(GC) or reference counts (non-GC). The bug occurs "
- "in non-GC mode";
- break;
- }
- }
-
- assert(GCModeDescription && "invalid/unknown GC mode");
- addExtraText(GCModeDescription);
-}
-
-static bool isNumericLiteralExpression(const Expr *E) {
- // FIXME: This set of cases was copied from SemaExprObjC.
- return isa<IntegerLiteral>(E) ||
- isa<CharacterLiteral>(E) ||
- isa<FloatingLiteral>(E) ||
- isa<ObjCBoolLiteralExpr>(E) ||
- isa<CXXBoolLiteralExpr>(E);
-}
-
-static Optional<std::string> describeRegion(const MemRegion *MR) {
- if (const auto *VR = dyn_cast_or_null<VarRegion>(MR))
- return std::string(VR->getDecl()->getName());
- // Once we support more storage locations for bindings,
- // this would need to be improved.
- return None;
-}
-
-/// Returns true if this stack frame is for an Objective-C method that is a
-/// property getter or setter whose body has been synthesized by the analyzer.
-static bool isSynthesizedAccessor(const StackFrameContext *SFC) {
- auto Method = dyn_cast_or_null<ObjCMethodDecl>(SFC->getDecl());
- if (!Method || !Method->isPropertyAccessor())
- return false;
-
- return SFC->getAnalysisDeclContext()->isBodyAutosynthesized();
-}
-
-std::shared_ptr<PathDiagnosticPiece>
-CFRefReportVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN,
- BugReporterContext &BRC, BugReport &BR) {
- // FIXME: We will eventually need to handle non-statement-based events
- // (__attribute__((cleanup))).
- if (!N->getLocation().getAs<StmtPoint>())
- return nullptr;
-
- // Check if the type state has changed.
- ProgramStateRef PrevSt = PrevN->getState();
- ProgramStateRef CurrSt = N->getState();
- const LocationContext *LCtx = N->getLocationContext();
-
- const RefVal* CurrT = getRefBinding(CurrSt, Sym);
- if (!CurrT) return nullptr;
-
- const RefVal &CurrV = *CurrT;
- const RefVal *PrevT = getRefBinding(PrevSt, Sym);
-
- // Create a string buffer to constain all the useful things we want
- // to tell the user.
- std::string sbuf;
- llvm::raw_string_ostream os(sbuf);
-
- // This is the allocation site since the previous node had no bindings
- // for this symbol.
- if (!PrevT) {
- const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
-
- if (isa<ObjCIvarRefExpr>(S) &&
- isSynthesizedAccessor(LCtx->getStackFrame())) {
- S = LCtx->getStackFrame()->getCallSite();
- }
-
- if (isa<ObjCArrayLiteral>(S)) {
- os << "NSArray literal is an object with a +0 retain count";
- }
- else if (isa<ObjCDictionaryLiteral>(S)) {
- os << "NSDictionary literal is an object with a +0 retain count";
- }
- else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) {
- if (isNumericLiteralExpression(BL->getSubExpr()))
- os << "NSNumber literal is an object with a +0 retain count";
- else {
- const ObjCInterfaceDecl *BoxClass = nullptr;
- if (const ObjCMethodDecl *Method = BL->getBoxingMethod())
- BoxClass = Method->getClassInterface();
-
- // We should always be able to find the boxing class interface,
- // but consider this future-proofing.
- if (BoxClass)
- os << *BoxClass << " b";
- else
- os << "B";
-
- os << "oxed expression produces an object with a +0 retain count";
- }
- }
- else if (isa<ObjCIvarRefExpr>(S)) {
- os << "Object loaded from instance variable";
- }
- else {
- if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
- // Get the name of the callee (if it is available).
- SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx);
- if (const FunctionDecl *FD = X.getAsFunctionDecl())
- os << "Call to function '" << *FD << '\'';
- else
- os << "function call";
- }
- else {
- assert(isa<ObjCMessageExpr>(S));
- CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
- CallEventRef<ObjCMethodCall> Call
- = Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx);
-
- switch (Call->getMessageKind()) {
- case OCM_Message:
- os << "Method";
- break;
- case OCM_PropertyAccess:
- os << "Property";
- break;
- case OCM_Subscript:
- os << "Subscript";
- break;
- }
- }
-
- if (CurrV.getObjKind() == RetEffect::CF) {
- os << " returns a Core Foundation object of type "
- << Sym->getType().getAsString() << " with a ";
- } else if (CurrV.getObjKind() == RetEffect::Generalized) {
- os << " returns an object of type " << Sym->getType().getAsString()
- << " with a ";
- } else {
- assert (CurrV.getObjKind() == RetEffect::ObjC);
- QualType T = Sym->getType();
- if (!isa<ObjCObjectPointerType>(T)) {
- os << " returns an Objective-C object with a ";
- } else {
- const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T);
- os << " returns an instance of "
- << PT->getPointeeType().getAsString() << " with a ";
- }
- }
-
- if (CurrV.isOwned()) {
- os << "+1 retain count";
-
- if (GCEnabled) {
- assert(CurrV.getObjKind() == RetEffect::CF);
- os << ". "
- "Core Foundation objects are not automatically garbage collected.";
- }
- }
- else {
- assert (CurrV.isNotOwned());
- os << "+0 retain count";
- }
- }
-
- PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
- N->getLocationContext());
- return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
- }
-
- // Gather up the effects that were performed on the object at this
- // program point
- SmallVector<ArgEffect, 2> AEffects;
-
- const ExplodedNode *OrigNode = BRC.getNodeResolver().getOriginalNode(N);
- if (const RetainSummary *Summ = SummaryLog.lookup(OrigNode)) {
- // We only have summaries attached to nodes after evaluating CallExpr and
- // ObjCMessageExprs.
- const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
-
- if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
- // Iterate through the parameter expressions and see if the symbol
- // was ever passed as an argument.
- unsigned i = 0;
-
- for (CallExpr::const_arg_iterator AI=CE->arg_begin(), AE=CE->arg_end();
- AI!=AE; ++AI, ++i) {
-
- // Retrieve the value of the argument. Is it the symbol
- // we are interested in?
- if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym)
- continue;
-
- // We have an argument. Get the effect!
- AEffects.push_back(Summ->getArg(i));
- }
- }
- else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {
- if (const Expr *receiver = ME->getInstanceReceiver())
- if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx)
- .getAsLocSymbol() == Sym) {
- // The symbol we are tracking is the receiver.
- AEffects.push_back(Summ->getReceiverEffect());
- }
- }
- }
-
- do {
- // Get the previous type state.
- RefVal PrevV = *PrevT;
-
- // Specially handle -dealloc.
- if (!GCEnabled && std::find(AEffects.begin(), AEffects.end(), Dealloc) !=
- AEffects.end()) {
- // Determine if the object's reference count was pushed to zero.
- assert(!PrevV.hasSameState(CurrV) && "The state should have changed.");
- // We may not have transitioned to 'release' if we hit an error.
- // This case is handled elsewhere.
- if (CurrV.getKind() == RefVal::Released) {
- assert(CurrV.getCombinedCounts() == 0);
- os << "Object released by directly sending the '-dealloc' message";
- break;
- }
- }
-
- // Specially handle CFMakeCollectable and friends.
- if (std::find(AEffects.begin(), AEffects.end(), MakeCollectable) !=
- AEffects.end()) {
- // Get the name of the function.
- const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
- SVal X =
- CurrSt->getSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee(), LCtx);
- const FunctionDecl *FD = X.getAsFunctionDecl();
-
- if (GCEnabled) {
- // Determine if the object's reference count was pushed to zero.
- assert(!PrevV.hasSameState(CurrV) && "The state should have changed.");
-
- os << "In GC mode a call to '" << *FD
- << "' decrements an object's retain count and registers the "
- "object with the garbage collector. ";
-
- if (CurrV.getKind() == RefVal::Released) {
- assert(CurrV.getCount() == 0);
- os << "Since it now has a 0 retain count the object can be "
- "automatically collected by the garbage collector.";
- }
- else
- os << "An object must have a 0 retain count to be garbage collected. "
- "After this call its retain count is +" << CurrV.getCount()
- << '.';
- }
- else
- os << "When GC is not enabled a call to '" << *FD
- << "' has no effect on its argument.";
-
- // Nothing more to say.
- break;
- }
-
- // Determine if the typestate has changed.
- if (!PrevV.hasSameState(CurrV))
- switch (CurrV.getKind()) {
- case RefVal::Owned:
- case RefVal::NotOwned:
- if (PrevV.getCount() == CurrV.getCount()) {
- // Did an autorelease message get sent?
- if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount())
- return nullptr;
-
- assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount());
- os << "Object autoreleased";
- break;
- }
-
- if (PrevV.getCount() > CurrV.getCount())
- os << "Reference count decremented.";
- else
- os << "Reference count incremented.";
-
- if (unsigned Count = CurrV.getCount())
- os << " The object now has a +" << Count << " retain count.";
-
- if (PrevV.getKind() == RefVal::Released) {
- assert(GCEnabled && CurrV.getCount() > 0);
- os << " The object is not eligible for garbage collection until "
- "the retain count reaches 0 again.";
- }
-
- break;
-
- case RefVal::Released:
- if (CurrV.getIvarAccessHistory() ==
- RefVal::IvarAccessHistory::ReleasedAfterDirectAccess &&
- CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) {
- os << "Strong instance variable relinquished. ";
- }
- os << "Object released.";
- break;
-
- case RefVal::ReturnedOwned:
- // Autoreleases can be applied after marking a node ReturnedOwned.
- if (CurrV.getAutoreleaseCount())
- return nullptr;
-
- os << "Object returned to caller as an owning reference (single "
- "retain count transferred to caller)";
- break;
-
- case RefVal::ReturnedNotOwned:
- os << "Object returned to caller with a +0 retain count";
- break;
-
- default:
- return nullptr;
- }
-
- // Emit any remaining diagnostics for the argument effects (if any).
- for (SmallVectorImpl<ArgEffect>::iterator I=AEffects.begin(),
- E=AEffects.end(); I != E; ++I) {
-
- // A bunch of things have alternate behavior under GC.
- if (GCEnabled)
- switch (*I) {
- default: break;
- case Autorelease:
- os << "In GC mode an 'autorelease' has no effect.";
- continue;
- case IncRefMsg:
- os << "In GC mode the 'retain' message has no effect.";
- continue;
- case DecRefMsg:
- os << "In GC mode the 'release' message has no effect.";
- continue;
- }
- }
- } while (0);
-
- if (os.str().empty())
- return nullptr; // We have nothing to say!
-
- const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
- PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
- N->getLocationContext());
- auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
-
- // Add the range by scanning the children of the statement for any bindings
- // to Sym.
- for (const Stmt *Child : S->children())
- if (const Expr *Exp = dyn_cast_or_null<Expr>(Child))
- if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) {
- P->addRange(Exp->getSourceRange());
- break;
- }
-
- return std::move(P);
-}
-
-namespace {
-// Find the first node in the current function context that referred to the
-// tracked symbol and the memory location that value was stored to. Note, the
-// value is only reported if the allocation occurred in the same function as
-// the leak. The function can also return a location context, which should be
-// treated as interesting.
-struct AllocationInfo {
- const ExplodedNode* N;
- const MemRegion *R;
- const LocationContext *InterestingMethodContext;
- AllocationInfo(const ExplodedNode *InN,
- const MemRegion *InR,
- const LocationContext *InInterestingMethodContext) :
- N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {}
-};
-} // end anonymous namespace
-
-static AllocationInfo
-GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N,
- SymbolRef Sym) {
- const ExplodedNode *AllocationNode = N;
- const ExplodedNode *AllocationNodeInCurrentOrParentContext = N;
- const MemRegion *FirstBinding = nullptr;
- const LocationContext *LeakContext = N->getLocationContext();
-
- // The location context of the init method called on the leaked object, if
- // available.
- const LocationContext *InitMethodContext = nullptr;
-
- while (N) {
- ProgramStateRef St = N->getState();
- const LocationContext *NContext = N->getLocationContext();
-
- if (!getRefBinding(St, Sym))
- break;
-
- StoreManager::FindUniqueBinding FB(Sym);
- StateMgr.iterBindings(St, FB);
-
- if (FB) {
- const MemRegion *R = FB.getRegion();
- const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>();
- // Do not show local variables belonging to a function other than
- // where the error is reported.
- if (!VR || VR->getStackFrame() == LeakContext->getStackFrame())
- FirstBinding = R;
- }
-
- // AllocationNode is the last node in which the symbol was tracked.
- AllocationNode = N;
-
- // AllocationNodeInCurrentContext, is the last node in the current or
- // parent context in which the symbol was tracked.
- //
- // Note that the allocation site might be in the parent conext. For example,
- // the case where an allocation happens in a block that captures a reference
- // to it and that reference is overwritten/dropped by another call to
- // the block.
- if (NContext == LeakContext || NContext->isParentOf(LeakContext))
- AllocationNodeInCurrentOrParentContext = N;
-
- // Find the last init that was called on the given symbol and store the
- // init method's location context.
- if (!InitMethodContext)
- if (Optional<CallEnter> CEP = N->getLocation().getAs<CallEnter>()) {
- const Stmt *CE = CEP->getCallExpr();
- if (const ObjCMessageExpr *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) {
- const Stmt *RecExpr = ME->getInstanceReceiver();
- if (RecExpr) {
- SVal RecV = St->getSVal(RecExpr, NContext);
- if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym)
- InitMethodContext = CEP->getCalleeContext();
- }
- }
- }
-
- N = N->pred_empty() ? nullptr : *(N->pred_begin());
- }
-
- // If we are reporting a leak of the object that was allocated with alloc,
- // mark its init method as interesting.
- const LocationContext *InterestingMethodContext = nullptr;
- if (InitMethodContext) {
- const ProgramPoint AllocPP = AllocationNode->getLocation();
- if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>())
- if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>())
- if (ME->getMethodFamily() == OMF_alloc)
- InterestingMethodContext = InitMethodContext;
- }
-
- // If allocation happened in a function different from the leak node context,
- // do not report the binding.
- assert(N && "Could not find allocation node");
- if (N->getLocationContext() != LeakContext) {
- FirstBinding = nullptr;
- }
-
- return AllocationInfo(AllocationNodeInCurrentOrParentContext,
- FirstBinding,
- InterestingMethodContext);
-}
-
-std::shared_ptr<PathDiagnosticPiece>
-CFRefReportVisitor::getEndPath(BugReporterContext &BRC,
- const ExplodedNode *EndN, BugReport &BR) {
- BR.markInteresting(Sym);
- return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR);
-}
-
-std::shared_ptr<PathDiagnosticPiece>
-CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
- const ExplodedNode *EndN, BugReport &BR) {
-
- // Tell the BugReporterContext to report cases when the tracked symbol is
- // assigned to different variables, etc.
- BR.markInteresting(Sym);
-
- // We are reporting a leak. Walk up the graph to get to the first node where
- // the symbol appeared, and also get the first VarDecl that tracked object
- // is stored to.
- AllocationInfo AllocI =
- GetAllocationSite(BRC.getStateManager(), EndN, Sym);
-
- const MemRegion* FirstBinding = AllocI.R;
- BR.markInteresting(AllocI.InterestingMethodContext);
-
- SourceManager& SM = BRC.getSourceManager();
-
- // Compute an actual location for the leak. Sometimes a leak doesn't
- // occur at an actual statement (e.g., transition between blocks; end
- // of function) so we need to walk the graph and compute a real location.
- const ExplodedNode *LeakN = EndN;
- PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM);
-
- std::string sbuf;
- llvm::raw_string_ostream os(sbuf);
-
- os << "Object leaked: ";
-
- Optional<std::string> RegionDescription = describeRegion(FirstBinding);
- if (RegionDescription) {
- os << "object allocated and stored into '" << *RegionDescription << '\'';
- }
- else
- os << "allocated object";
-
- // Get the retain count.
- const RefVal* RV = getRefBinding(EndN->getState(), Sym);
- assert(RV);
-
- if (RV->getKind() == RefVal::ErrorLeakReturned) {
- // FIXME: Per comments in rdar://6320065, "create" only applies to CF
- // objects. Only "copy", "alloc", "retain" and "new" transfer ownership
- // to the caller for NS objects.
- const Decl *D = &EndN->getCodeDecl();
-
- os << (isa<ObjCMethodDecl>(D) ? " is returned from a method "
- : " is returned from a function ");
-
- if (D->hasAttr<CFReturnsNotRetainedAttr>())
- os << "that is annotated as CF_RETURNS_NOT_RETAINED";
- else if (D->hasAttr<NSReturnsNotRetainedAttr>())
- os << "that is annotated as NS_RETURNS_NOT_RETAINED";
- else {
- if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
- if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) {
- os << "managed by Automatic Reference Counting";
- } else {
- os << "whose name ('" << MD->getSelector().getAsString()
- << "') does not start with "
- "'copy', 'mutableCopy', 'alloc' or 'new'."
- " This violates the naming convention rules"
- " given in the Memory Management Guide for Cocoa";
- }
- }
- else {
- const FunctionDecl *FD = cast<FunctionDecl>(D);
- os << "whose name ('" << *FD
- << "') does not contain 'Copy' or 'Create'. This violates the naming"
- " convention rules given in the Memory Management Guide for Core"
- " Foundation";
- }
- }
- }
- else if (RV->getKind() == RefVal::ErrorGCLeakReturned) {
- const ObjCMethodDecl &MD = cast<ObjCMethodDecl>(EndN->getCodeDecl());
- os << " and returned from method '" << MD.getSelector().getAsString()
- << "' is potentially leaked when using garbage collection. Callers "
- "of this method do not expect a returned object with a +1 retain "
- "count since they expect the object to be managed by the garbage "
- "collector";
- }
- else
- os << " is not referenced later in this execution path and has a retain "
- "count of +" << RV->getCount();
-
- return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
-}
-
-void CFRefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) {
- const SourceManager& SMgr = Ctx.getSourceManager();
-
- if (!sym->getOriginRegion())
- return;
-
- auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion());
- if (Region) {
- const Decl *PDecl = Region->getDecl();
- if (PDecl && isa<ParmVarDecl>(PDecl)) {
- PathDiagnosticLocation ParamLocation = PathDiagnosticLocation::create(PDecl, SMgr);
- Location = ParamLocation;
- UniqueingLocation = ParamLocation;
- UniqueingDecl = Ctx.getLocationContext()->getDecl();
- }
- }
-}
-
-void CFRefLeakReport::deriveAllocLocation(CheckerContext &Ctx,SymbolRef sym) {
- // Most bug reports are cached at the location where they occurred.
- // With leaks, we want to unique them by the location where they were
- // allocated, and only report a single path. To do this, we need to find
- // the allocation site of a piece of tracked memory, which we do via a
- // call to GetAllocationSite. This will walk the ExplodedGraph backwards.
- // Note that this is *not* the trimmed graph; we are guaranteed, however,
- // that all ancestor nodes that represent the allocation site have the
- // same SourceLocation.
- const ExplodedNode *AllocNode = nullptr;
-
- const SourceManager& SMgr = Ctx.getSourceManager();
-
- AllocationInfo AllocI =
- GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym);
-
- AllocNode = AllocI.N;
- AllocBinding = AllocI.R;
- markInteresting(AllocI.InterestingMethodContext);
-
- // Get the SourceLocation for the allocation site.
- // FIXME: This will crash the analyzer if an allocation comes from an
- // implicit call (ex: a destructor call).
- // (Currently there are no such allocations in Cocoa, though.)
- AllocStmt = PathDiagnosticLocation::getStmt(AllocNode);
-
- if (!AllocStmt) {
- AllocBinding = nullptr;
- return;
- }
-
- PathDiagnosticLocation AllocLocation =
- PathDiagnosticLocation::createBegin(AllocStmt, SMgr,
- AllocNode->getLocationContext());
- Location = AllocLocation;
-
- // Set uniqieing info, which will be used for unique the bug reports. The
- // leaks should be uniqued on the allocation site.
- UniqueingLocation = AllocLocation;
- UniqueingDecl = AllocNode->getLocationContext()->getDecl();
-}
-
-void CFRefLeakReport::createDescription(CheckerContext &Ctx, bool GCEnabled,
- bool IncludeAllocationLine) {
- assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid());
- Description.clear();
- llvm::raw_string_ostream os(Description);
- os << "Potential leak ";
- if (GCEnabled)
- os << "(when using garbage collection) ";
- os << "of an object";
-
- Optional<std::string> RegionDescription = describeRegion(AllocBinding);
- if (RegionDescription) {
- os << " stored into '" << *RegionDescription << '\'';
- if (IncludeAllocationLine) {
- FullSourceLoc SL(AllocStmt->getLocStart(), Ctx.getSourceManager());
- os << " (allocated on line " << SL.getSpellingLineNumber() << ")";
- }
- }
-}
-
-CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts,
- bool GCEnabled, const SummaryLogTy &Log,
- ExplodedNode *n, SymbolRef sym,
- CheckerContext &Ctx,
- bool IncludeAllocationLine)
- : CFRefReport(D, LOpts, GCEnabled, Log, n, sym, false) {
-
- deriveAllocLocation(Ctx, sym);
- if (!AllocBinding)
- deriveParamLocation(Ctx, sym);
-
- createDescription(Ctx, GCEnabled, IncludeAllocationLine);
-
- addVisitor(llvm::make_unique<CFRefLeakReportVisitor>(sym, GCEnabled, Log));
-}
-
-//===----------------------------------------------------------------------===//
-// Main checker logic.
-//===----------------------------------------------------------------------===//
-
-namespace {
-class RetainCountChecker
- : public Checker< check::Bind,
- check::DeadSymbols,
- check::EndAnalysis,
- check::BeginFunction,
- check::EndFunction,
- check::PostStmt<BlockExpr>,
- check::PostStmt<CastExpr>,
- check::PostStmt<ObjCArrayLiteral>,
- check::PostStmt<ObjCDictionaryLiteral>,
- check::PostStmt<ObjCBoxedExpr>,
- check::PostStmt<ObjCIvarRefExpr>,
- check::PostCall,
- check::PreStmt<ReturnStmt>,
- check::RegionChanges,
- eval::Assume,
- eval::Call > {
- mutable std::unique_ptr<CFRefBug> useAfterRelease, releaseNotOwned;
- mutable std::unique_ptr<CFRefBug> deallocGC, deallocNotOwned;
- mutable std::unique_ptr<CFRefBug> overAutorelease, returnNotOwnedForOwned;
- mutable std::unique_ptr<CFRefBug> leakWithinFunction, leakAtReturn;
- mutable std::unique_ptr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC;
-
- typedef llvm::DenseMap<SymbolRef, const CheckerProgramPointTag *> SymbolTagMap;
-
- // This map is only used to ensure proper deletion of any allocated tags.
- mutable SymbolTagMap DeadSymbolTags;
-
- mutable std::unique_ptr<RetainSummaryManager> Summaries;
- mutable std::unique_ptr<RetainSummaryManager> SummariesGC;
- mutable SummaryLogTy SummaryLog;
- mutable bool ShouldResetSummaryLog;
-
- /// Optional setting to indicate if leak reports should include
- /// the allocation line.
- mutable bool IncludeAllocationLine;
-
-public:
- RetainCountChecker(AnalyzerOptions &AO)
- : ShouldResetSummaryLog(false),
- IncludeAllocationLine(shouldIncludeAllocationSiteInLeakDiagnostics(AO)) {}
-
- ~RetainCountChecker() override { DeleteContainerSeconds(DeadSymbolTags); }
-
- void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR,
- ExprEngine &Eng) const {
- // FIXME: This is a hack to make sure the summary log gets cleared between
- // analyses of different code bodies.
- //
- // Why is this necessary? Because a checker's lifetime is tied to a
- // translation unit, but an ExplodedGraph's lifetime is just a code body.
- // Once in a blue moon, a new ExplodedNode will have the same address as an
- // old one with an associated summary, and the bug report visitor gets very
- // confused. (To make things worse, the summary lifetime is currently also
- // tied to a code body, so we get a crash instead of incorrect results.)
- //
- // Why is this a bad solution? Because if the lifetime of the ExplodedGraph
- // changes, things will start going wrong again. Really the lifetime of this
- // log needs to be tied to either the specific nodes in it or the entire
- // ExplodedGraph, not to a specific part of the code being analyzed.
- //
- // (Also, having stateful local data means that the same checker can't be
- // used from multiple threads, but a lot of checkers have incorrect
- // assumptions about that anyway. So that wasn't a priority at the time of
- // this fix.)
- //
- // This happens at the end of analysis, but bug reports are emitted /after/
- // this point. So we can't just clear the summary log now. Instead, we mark
- // that the next time we access the summary log, it should be cleared.
-
- // If we never reset the summary log during /this/ code body analysis,
- // there were no new summaries. There might still have been summaries from
- // the /last/ analysis, so clear them out to make sure the bug report
- // visitors don't get confused.
- if (ShouldResetSummaryLog)
- SummaryLog.clear();
-
- ShouldResetSummaryLog = !SummaryLog.empty();
- }
-
- CFRefBug *getLeakWithinFunctionBug(const LangOptions &LOpts,
- bool GCEnabled) const {
- if (GCEnabled) {
- if (!leakWithinFunctionGC)
- leakWithinFunctionGC.reset(new Leak(this, "Leak of object when using "
- "garbage collection"));
- return leakWithinFunctionGC.get();
- } else {
- if (!leakWithinFunction) {
- if (LOpts.getGC() == LangOptions::HybridGC) {
- leakWithinFunction.reset(new Leak(this,
- "Leak of object when not using "
- "garbage collection (GC) in "
- "dual GC/non-GC code"));
- } else {
- leakWithinFunction.reset(new Leak(this, "Leak"));
- }
- }
- return leakWithinFunction.get();
- }
- }
-
- CFRefBug *getLeakAtReturnBug(const LangOptions &LOpts, bool GCEnabled) const {
- if (GCEnabled) {
- if (!leakAtReturnGC)
- leakAtReturnGC.reset(new Leak(this,
- "Leak of returned object when using "
- "garbage collection"));
- return leakAtReturnGC.get();
- } else {
- if (!leakAtReturn) {
- if (LOpts.getGC() == LangOptions::HybridGC) {
- leakAtReturn.reset(new Leak(this,
- "Leak of returned object when not using "
- "garbage collection (GC) in dual "
- "GC/non-GC code"));
- } else {
- leakAtReturn.reset(new Leak(this, "Leak of returned object"));
- }
- }
- return leakAtReturn.get();
- }
- }
-
- RetainSummaryManager &getSummaryManager(ASTContext &Ctx,
- bool GCEnabled) const {
- // FIXME: We don't support ARC being turned on and off during one analysis.
- // (nor, for that matter, do we support changing ASTContexts)
- bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount;
- if (GCEnabled) {
- if (!SummariesGC)
- SummariesGC.reset(new RetainSummaryManager(Ctx, true, ARCEnabled));
- else
- assert(SummariesGC->isARCEnabled() == ARCEnabled);
- return *SummariesGC;
- } else {
- if (!Summaries)
- Summaries.reset(new RetainSummaryManager(Ctx, false, ARCEnabled));
- else
- assert(Summaries->isARCEnabled() == ARCEnabled);
- return *Summaries;
- }
- }
-
- RetainSummaryManager &getSummaryManager(CheckerContext &C) const {
- return getSummaryManager(C.getASTContext(), C.isObjCGCEnabled());
- }
-
- void printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const override;
-
- void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const;
- void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
- void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
-
- void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const;
- void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const;
- void checkPostStmt(const ObjCBoxedExpr *BE, CheckerContext &C) const;
-
- void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const;
-
- void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
-
- void checkSummary(const RetainSummary &Summ, const CallEvent &Call,
- CheckerContext &C) const;
-
- void processSummaryOfInlined(const RetainSummary &Summ,
- const CallEvent &Call,
- CheckerContext &C) const;
-
- bool evalCall(const CallExpr *CE, CheckerContext &C) const;
-
- ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
- bool Assumption) const;
-
- ProgramStateRef
- checkRegionChanges(ProgramStateRef state,
- const InvalidatedSymbols *invalidated,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions,
- const LocationContext* LCtx,
- const CallEvent *Call) const;
-
- void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const;
- void checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C,
- ExplodedNode *Pred, RetEffect RE, RefVal X,
- SymbolRef Sym, ProgramStateRef state) const;
-
- void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
- void checkBeginFunction(CheckerContext &C) const;
- void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
-
- ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym,
- RefVal V, ArgEffect E, RefVal::Kind &hasErr,
- CheckerContext &C) const;
-
- void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange,
- RefVal::Kind ErrorKind, SymbolRef Sym,
- CheckerContext &C) const;
-
- void processObjCLiterals(CheckerContext &C, const Expr *Ex) const;
-
- const ProgramPointTag *getDeadSymbolTag(SymbolRef sym) const;
-
- ProgramStateRef handleSymbolDeath(ProgramStateRef state,
- SymbolRef sid, RefVal V,
- SmallVectorImpl<SymbolRef> &Leaked) const;
-
- ProgramStateRef
- handleAutoreleaseCounts(ProgramStateRef state, ExplodedNode *Pred,
- const ProgramPointTag *Tag, CheckerContext &Ctx,
- SymbolRef Sym, RefVal V) const;
-
- ExplodedNode *processLeaks(ProgramStateRef state,
- SmallVectorImpl<SymbolRef> &Leaked,
- CheckerContext &Ctx,
- ExplodedNode *Pred = nullptr) const;
-};
-} // end anonymous namespace
-
-namespace {
-class StopTrackingCallback final : public SymbolVisitor {
- ProgramStateRef state;
-public:
- StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
- ProgramStateRef getState() const { return state; }
-
- bool VisitSymbol(SymbolRef sym) override {
- state = state->remove<RefBindings>(sym);
- return true;
- }
-};
-} // end anonymous namespace
-
-//===----------------------------------------------------------------------===//
-// Handle statements that may have an effect on refcounts.
-//===----------------------------------------------------------------------===//
-
-void RetainCountChecker::checkPostStmt(const BlockExpr *BE,
- CheckerContext &C) const {
-
- // Scan the BlockDecRefExprs for any object the retain count checker
- // may be tracking.
- if (!BE->getBlockDecl()->hasCaptures())
- return;
-
- ProgramStateRef state = C.getState();
- auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion());
-
- BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(),
- E = R->referenced_vars_end();
-
- if (I == E)
- return;
-
- // FIXME: For now we invalidate the tracking of all symbols passed to blocks
- // via captured variables, even though captured variables result in a copy
- // and in implicit increment/decrement of a retain count.
- SmallVector<const MemRegion*, 10> Regions;
- const LocationContext *LC = C.getLocationContext();
- MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager();
-
- for ( ; I != E; ++I) {
- const VarRegion *VR = I.getCapturedRegion();
- if (VR->getSuperRegion() == R) {
- VR = MemMgr.getVarRegion(VR->getDecl(), LC);
- }
- Regions.push_back(VR);
- }
-
- state =
- state->scanReachableSymbols<StopTrackingCallback>(Regions.data(),
- Regions.data() + Regions.size()).getState();
- C.addTransition(state);
-}
-
-void RetainCountChecker::checkPostStmt(const CastExpr *CE,
- CheckerContext &C) const {
- const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE);
- if (!BE)
- return;
-
- ArgEffect AE = IncRef;
-
- switch (BE->getBridgeKind()) {
- case clang::OBC_Bridge:
- // Do nothing.
- return;
- case clang::OBC_BridgeRetained:
- AE = IncRef;
- break;
- case clang::OBC_BridgeTransfer:
- AE = DecRefBridgedTransferred;
- break;
- }
-
- ProgramStateRef state = C.getState();
- SymbolRef Sym = C.getSVal(CE).getAsLocSymbol();
- if (!Sym)
- return;
- const RefVal* T = getRefBinding(state, Sym);
- if (!T)
- return;
-
- RefVal::Kind hasErr = (RefVal::Kind) 0;
- state = updateSymbol(state, Sym, *T, AE, hasErr, C);
-
- if (hasErr) {
- // FIXME: If we get an error during a bridge cast, should we report it?
- return;
- }
-
- C.addTransition(state);
-}
-
-void RetainCountChecker::processObjCLiterals(CheckerContext &C,
- const Expr *Ex) const {
- ProgramStateRef state = C.getState();
- const ExplodedNode *pred = C.getPredecessor();
- for (const Stmt *Child : Ex->children()) {
- SVal V = pred->getSVal(Child);
- if (SymbolRef sym = V.getAsSymbol())
- if (const RefVal* T = getRefBinding(state, sym)) {
- RefVal::Kind hasErr = (RefVal::Kind) 0;
- state = updateSymbol(state, sym, *T, MayEscape, hasErr, C);
- if (hasErr) {
- processNonLeakError(state, Child->getSourceRange(), hasErr, sym, C);
- return;
- }
- }
- }
-
- // Return the object as autoreleased.
- // RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC);
- if (SymbolRef sym =
- state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) {
- QualType ResultTy = Ex->getType();
- state = setRefBinding(state, sym,
- RefVal::makeNotOwned(RetEffect::ObjC, ResultTy));
- }
-
- C.addTransition(state);
-}
-
-void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL,
- CheckerContext &C) const {
- // Apply the 'MayEscape' to all values.
- processObjCLiterals(C, AL);
-}
-
-void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
- CheckerContext &C) const {
- // Apply the 'MayEscape' to all keys and values.
- processObjCLiterals(C, DL);
-}
-
-void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex,
- CheckerContext &C) const {
- const ExplodedNode *Pred = C.getPredecessor();
- ProgramStateRef State = Pred->getState();
-
- if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) {
- QualType ResultTy = Ex->getType();
- State = setRefBinding(State, Sym,
- RefVal::makeNotOwned(RetEffect::ObjC, ResultTy));
- }
-
- C.addTransition(State);
-}
-
-void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE,
- CheckerContext &C) const {
- Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>();
- if (!IVarLoc)
- return;
-
- ProgramStateRef State = C.getState();
- SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol();
- if (!Sym || !dyn_cast_or_null<ObjCIvarRegion>(Sym->getOriginRegion()))
- return;
-
- // Accessing an ivar directly is unusual. If we've done that, be more
- // forgiving about what the surrounding code is allowed to do.
-
- QualType Ty = Sym->getType();
- RetEffect::ObjKind Kind;
- if (Ty->isObjCRetainableType())
- Kind = RetEffect::ObjC;
- else if (coreFoundation::isCFObjectRef(Ty))
- Kind = RetEffect::CF;
- else
- return;
-
- // If the value is already known to be nil, don't bother tracking it.
- ConstraintManager &CMgr = State->getConstraintManager();
- if (CMgr.isNull(State, Sym).isConstrainedTrue())
- return;
-
- if (const RefVal *RV = getRefBinding(State, Sym)) {
- // If we've seen this symbol before, or we're only seeing it now because
- // of something the analyzer has synthesized, don't do anything.
- if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None ||
- isSynthesizedAccessor(C.getStackFrame())) {
- return;
- }
-
- // Note that this value has been loaded from an ivar.
- C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess()));
- return;
- }
-
- RefVal PlusZero = RefVal::makeNotOwned(Kind, Ty);
-
- // In a synthesized accessor, the effective retain count is +0.
- if (isSynthesizedAccessor(C.getStackFrame())) {
- C.addTransition(setRefBinding(State, Sym, PlusZero));
- return;
- }
-
- State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
- C.addTransition(State);
-}
-
-void RetainCountChecker::checkPostCall(const CallEvent &Call,
- CheckerContext &C) const {
- RetainSummaryManager &Summaries = getSummaryManager(C);
- const RetainSummary *Summ = Summaries.getSummary(Call, C.getState());
-
- if (C.wasInlined) {
- processSummaryOfInlined(*Summ, Call, C);
- return;
- }
- checkSummary(*Summ, Call, C);
-}
-
-/// GetReturnType - Used to get the return type of a message expression or
-/// function call with the intention of affixing that type to a tracked symbol.
-/// While the return type can be queried directly from RetEx, when
-/// invoking class methods we augment to the return type to be that of
-/// a pointer to the class (as opposed it just being id).
-// FIXME: We may be able to do this with related result types instead.
-// This function is probably overestimating.
-static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) {
- QualType RetTy = RetE->getType();
- // If RetE is not a message expression just return its type.
- // If RetE is a message expression, return its types if it is something
- /// more specific than id.
- if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(RetE))
- if (const ObjCObjectPointerType *PT = RetTy->getAs<ObjCObjectPointerType>())
- if (PT->isObjCQualifiedIdType() || PT->isObjCIdType() ||
- PT->isObjCClassType()) {
- // At this point we know the return type of the message expression is
- // id, id<...>, or Class. If we have an ObjCInterfaceDecl, we know this
- // is a call to a class method whose type we can resolve. In such
- // cases, promote the return type to XXX* (where XXX is the class).
- const ObjCInterfaceDecl *D = ME->getReceiverInterface();
- return !D ? RetTy :
- Ctx.getObjCObjectPointerType(Ctx.getObjCInterfaceType(D));
- }
-
- return RetTy;
-}
-
-// We don't always get the exact modeling of the function with regards to the
-// retain count checker even when the function is inlined. For example, we need
-// to stop tracking the symbols which were marked with StopTrackingHard.
-void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
- const CallEvent &CallOrMsg,
- CheckerContext &C) const {
- ProgramStateRef state = C.getState();
-
- // Evaluate the effect of the arguments.
- for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
- if (Summ.getArg(idx) == StopTrackingHard) {
- SVal V = CallOrMsg.getArgSVal(idx);
- if (SymbolRef Sym = V.getAsLocSymbol()) {
- state = removeRefBinding(state, Sym);
- }
- }
- }
-
- // Evaluate the effect on the message receiver.
- const ObjCMethodCall *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg);
- if (MsgInvocation) {
- if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
- if (Summ.getReceiverEffect() == StopTrackingHard) {
- state = removeRefBinding(state, Sym);
- }
- }
- }
-
- // Consult the summary for the return value.
- RetEffect RE = Summ.getRetEffect();
- if (RE.getKind() == RetEffect::NoRetHard) {
- SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol();
- if (Sym)
- state = removeRefBinding(state, Sym);
- }
-
- C.addTransition(state);
-}
-
-static ProgramStateRef updateOutParameter(ProgramStateRef State,
- SVal ArgVal,
- ArgEffect Effect) {
- auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
- if (!ArgRegion)
- return State;
-
- QualType PointeeTy = ArgRegion->getValueType();
- if (!coreFoundation::isCFObjectRef(PointeeTy))
- return State;
-
- SVal PointeeVal = State->getSVal(ArgRegion);
- SymbolRef Pointee = PointeeVal.getAsLocSymbol();
- if (!Pointee)
- return State;
-
- switch (Effect) {
- case UnretainedOutParameter:
- State = setRefBinding(State, Pointee,
- RefVal::makeNotOwned(RetEffect::CF, PointeeTy));
- break;
- case RetainedOutParameter:
- // Do nothing. Retained out parameters will either point to a +1 reference
- // or NULL, but the way you check for failure differs depending on the API.
- // Consequently, we don't have a good way to track them yet.
- break;
-
- default:
- llvm_unreachable("only for out parameters");
- }
-
- return State;
-}
-
-void RetainCountChecker::checkSummary(const RetainSummary &Summ,
- const CallEvent &CallOrMsg,
- CheckerContext &C) const {
- ProgramStateRef state = C.getState();
-
- // Evaluate the effect of the arguments.
- RefVal::Kind hasErr = (RefVal::Kind) 0;
- SourceRange ErrorRange;
- SymbolRef ErrorSym = nullptr;
-
- for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
- SVal V = CallOrMsg.getArgSVal(idx);
-
- ArgEffect Effect = Summ.getArg(idx);
- if (Effect == RetainedOutParameter || Effect == UnretainedOutParameter) {
- state = updateOutParameter(state, V, Effect);
- } else if (SymbolRef Sym = V.getAsLocSymbol()) {
- if (const RefVal *T = getRefBinding(state, Sym)) {
- state = updateSymbol(state, Sym, *T, Effect, hasErr, C);
- if (hasErr) {
- ErrorRange = CallOrMsg.getArgSourceRange(idx);
- ErrorSym = Sym;
- break;
- }
- }
- }
- }
-
- // Evaluate the effect on the message receiver.
- bool ReceiverIsTracked = false;
- if (!hasErr) {
- const ObjCMethodCall *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg);
- if (MsgInvocation) {
- if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
- if (const RefVal *T = getRefBinding(state, Sym)) {
- ReceiverIsTracked = true;
- state = updateSymbol(state, Sym, *T, Summ.getReceiverEffect(),
- hasErr, C);
- if (hasErr) {
- ErrorRange = MsgInvocation->getOriginExpr()->getReceiverRange();
- ErrorSym = Sym;
- }
- }
- }
- }
- }
-
- // Process any errors.
- if (hasErr) {
- processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C);
- return;
- }
-
- // Consult the summary for the return value.
- RetEffect RE = Summ.getRetEffect();
-
- if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) {
- if (ReceiverIsTracked)
- RE = getSummaryManager(C).getObjAllocRetEffect();
- else
- RE = RetEffect::MakeNoRet();
- }
-
- switch (RE.getKind()) {
- default:
- llvm_unreachable("Unhandled RetEffect.");
-
- case RetEffect::NoRet:
- case RetEffect::NoRetHard:
- // No work necessary.
- break;
-
- case RetEffect::OwnedSymbol: {
- SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol();
- if (!Sym)
- break;
-
- // Use the result type from the CallEvent as it automatically adjusts
- // for methods/functions that return references.
- QualType ResultTy = CallOrMsg.getResultType();
- state = setRefBinding(state, Sym, RefVal::makeOwned(RE.getObjKind(),
- ResultTy));
-
- // FIXME: Add a flag to the checker where allocations are assumed to
- // *not* fail.
- break;
- }
-
- case RetEffect::GCNotOwnedSymbol:
- case RetEffect::NotOwnedSymbol: {
- const Expr *Ex = CallOrMsg.getOriginExpr();
- SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol();
- if (!Sym)
- break;
- assert(Ex);
- // Use GetReturnType in order to give [NSFoo alloc] the type NSFoo *.
- QualType ResultTy = GetReturnType(Ex, C.getASTContext());
- state = setRefBinding(state, Sym, RefVal::makeNotOwned(RE.getObjKind(),
- ResultTy));
- break;
- }
- }
-
- // This check is actually necessary; otherwise the statement builder thinks
- // we've hit a previously-found path.
- // Normally addTransition takes care of this, but we want the node pointer.
- ExplodedNode *NewNode;
- if (state == C.getState()) {
- NewNode = C.getPredecessor();
- } else {
- NewNode = C.addTransition(state);
- }
-
- // Annotate the node with summary we used.
- if (NewNode) {
- // FIXME: This is ugly. See checkEndAnalysis for why it's necessary.
- if (ShouldResetSummaryLog) {
- SummaryLog.clear();
- ShouldResetSummaryLog = false;
- }
- SummaryLog[NewNode] = &Summ;
- }
-}
-
-ProgramStateRef
-RetainCountChecker::updateSymbol(ProgramStateRef state, SymbolRef sym,
- RefVal V, ArgEffect E, RefVal::Kind &hasErr,
- CheckerContext &C) const {
- // In GC mode [... release] and [... retain] do nothing.
- // In ARC mode they shouldn't exist at all, but we just ignore them.
- bool IgnoreRetainMsg = C.isObjCGCEnabled();
- if (!IgnoreRetainMsg)
- IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount;
-
- switch (E) {
- default:
- break;
- case IncRefMsg:
- E = IgnoreRetainMsg ? DoNothing : IncRef;
- break;
- case DecRefMsg:
- E = IgnoreRetainMsg ? DoNothing : DecRef;
- break;
- case DecRefMsgAndStopTrackingHard:
- E = IgnoreRetainMsg ? StopTracking : DecRefAndStopTrackingHard;
- break;
- case MakeCollectable:
- E = C.isObjCGCEnabled() ? DecRef : DoNothing;
- break;
- }
-
- // Handle all use-after-releases.
- if (!C.isObjCGCEnabled() && V.getKind() == RefVal::Released) {
- V = V ^ RefVal::ErrorUseAfterRelease;
- hasErr = V.getKind();
- return setRefBinding(state, sym, V);
- }
-
- switch (E) {
- case DecRefMsg:
- case IncRefMsg:
- case MakeCollectable:
- case DecRefMsgAndStopTrackingHard:
- llvm_unreachable("DecRefMsg/IncRefMsg/MakeCollectable already converted");
-
- case UnretainedOutParameter:
- case RetainedOutParameter:
- llvm_unreachable("Applies to pointer-to-pointer parameters, which should "
- "not have ref state.");
-
- case Dealloc:
- // Any use of -dealloc in GC is *bad*.
- if (C.isObjCGCEnabled()) {
- V = V ^ RefVal::ErrorDeallocGC;
- hasErr = V.getKind();
- break;
- }
-
- switch (V.getKind()) {
- default:
- llvm_unreachable("Invalid RefVal state for an explicit dealloc.");
- case RefVal::Owned:
- // The object immediately transitions to the released state.
- V = V ^ RefVal::Released;
- V.clearCounts();
- return setRefBinding(state, sym, V);
- case RefVal::NotOwned:
- V = V ^ RefVal::ErrorDeallocNotOwned;
- hasErr = V.getKind();
- break;
- }
- break;
-
- case MayEscape:
- if (V.getKind() == RefVal::Owned) {
- V = V ^ RefVal::NotOwned;
- break;
- }
-
- // Fall-through.
-
- case DoNothing:
- return state;
-
- case Autorelease:
- if (C.isObjCGCEnabled())
- return state;
- // Update the autorelease counts.
- V = V.autorelease();
- break;
-
- case StopTracking:
- case StopTrackingHard:
- return removeRefBinding(state, sym);
-
- case IncRef:
- switch (V.getKind()) {
- default:
- llvm_unreachable("Invalid RefVal state for a retain.");
- case RefVal::Owned:
- case RefVal::NotOwned:
- V = V + 1;
- break;
- case RefVal::Released:
- // Non-GC cases are handled above.
- assert(C.isObjCGCEnabled());
- V = (V ^ RefVal::Owned) + 1;
- break;
- }
- break;
-
- case DecRef:
- case DecRefBridgedTransferred:
- case DecRefAndStopTrackingHard:
- switch (V.getKind()) {
- default:
- // case 'RefVal::Released' handled above.
- llvm_unreachable("Invalid RefVal state for a release.");
-
- case RefVal::Owned:
- assert(V.getCount() > 0);
- if (V.getCount() == 1) {
- if (E == DecRefBridgedTransferred ||
- V.getIvarAccessHistory() ==
- RefVal::IvarAccessHistory::AccessedDirectly)
- V = V ^ RefVal::NotOwned;
- else
- V = V ^ RefVal::Released;
- } else if (E == DecRefAndStopTrackingHard) {
- return removeRefBinding(state, sym);
- }
-
- V = V - 1;
- break;
-
- case RefVal::NotOwned:
- if (V.getCount() > 0) {
- if (E == DecRefAndStopTrackingHard)
- return removeRefBinding(state, sym);
- V = V - 1;
- } else if (V.getIvarAccessHistory() ==
- RefVal::IvarAccessHistory::AccessedDirectly) {
- // Assume that the instance variable was holding on the object at
- // +1, and we just didn't know.
- if (E == DecRefAndStopTrackingHard)
- return removeRefBinding(state, sym);
- V = V.releaseViaIvar() ^ RefVal::Released;
- } else {
- V = V ^ RefVal::ErrorReleaseNotOwned;
- hasErr = V.getKind();
- }
- break;
-
- case RefVal::Released:
- // Non-GC cases are handled above.
- assert(C.isObjCGCEnabled());
- V = V ^ RefVal::ErrorUseAfterRelease;
- hasErr = V.getKind();
- break;
- }
- break;
- }
- return setRefBinding(state, sym, V);
-}
-
-void RetainCountChecker::processNonLeakError(ProgramStateRef St,
- SourceRange ErrorRange,
- RefVal::Kind ErrorKind,
- SymbolRef Sym,
- CheckerContext &C) const {
- // HACK: Ignore retain-count issues on values accessed through ivars,
- // because of cases like this:
- // [_contentView retain];
- // [_contentView removeFromSuperview];
- // [self addSubview:_contentView]; // invalidates 'self'
- // [_contentView release];
- if (const RefVal *RV = getRefBinding(St, Sym))
- if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
- return;
-
- ExplodedNode *N = C.generateErrorNode(St);
- if (!N)
- return;
-
- CFRefBug *BT;
- switch (ErrorKind) {
- default:
- llvm_unreachable("Unhandled error.");
- case RefVal::ErrorUseAfterRelease:
- if (!useAfterRelease)
- useAfterRelease.reset(new UseAfterRelease(this));
- BT = useAfterRelease.get();
- break;
- case RefVal::ErrorReleaseNotOwned:
- if (!releaseNotOwned)
- releaseNotOwned.reset(new BadRelease(this));
- BT = releaseNotOwned.get();
- break;
- case RefVal::ErrorDeallocGC:
- if (!deallocGC)
- deallocGC.reset(new DeallocGC(this));
- BT = deallocGC.get();
- break;
- case RefVal::ErrorDeallocNotOwned:
- if (!deallocNotOwned)
- deallocNotOwned.reset(new DeallocNotOwned(this));
- BT = deallocNotOwned.get();
- break;
- }
-
- assert(BT);
- auto report = std::unique_ptr<BugReport>(
- new CFRefReport(*BT, C.getASTContext().getLangOpts(), C.isObjCGCEnabled(),
- SummaryLog, N, Sym));
- report->addRange(ErrorRange);
- C.emitReport(std::move(report));
-}
-
-//===----------------------------------------------------------------------===//
-// Handle the return values of retain-count-related functions.
-//===----------------------------------------------------------------------===//
-
-bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
- // Get the callee. We're only interested in simple C functions.
- ProgramStateRef state = C.getState();
- const FunctionDecl *FD = C.getCalleeDecl(CE);
- if (!FD)
- return false;
-
- IdentifierInfo *II = FD->getIdentifier();
- if (!II)
- return false;
-
- // For now, we're only handling the functions that return aliases of their
- // arguments: CFRetain and CFMakeCollectable (and their families).
- // Eventually we should add other functions we can model entirely,
- // such as CFRelease, which don't invalidate their arguments or globals.
- if (CE->getNumArgs() != 1)
- return false;
-
- // Get the name of the function.
- StringRef FName = II->getName();
- FName = FName.substr(FName.find_first_not_of('_'));
-
- // See if it's one of the specific functions we know how to eval.
- bool canEval = false;
- // See if the function has 'rc_ownership_trusted_implementation'
- // annotate attribute. If it does, we will not inline it.
- bool hasTrustedImplementationAnnotation = false;
-
- QualType ResultTy = CE->getCallReturnType(C.getASTContext());
- if (ResultTy->isObjCIdType()) {
- // Handle: id NSMakeCollectable(CFTypeRef)
- canEval = II->isStr("NSMakeCollectable");
- } else if (ResultTy->isPointerType()) {
- // Handle: (CF|CG|CV)Retain
- // CFAutorelease
- // CFMakeCollectable
- // It's okay to be a little sloppy here (CGMakeCollectable doesn't exist).
- if (cocoa::isRefType(ResultTy, "CF", FName) ||
- cocoa::isRefType(ResultTy, "CG", FName) ||
- cocoa::isRefType(ResultTy, "CV", FName)) {
- canEval = isRetain(FD, FName) || isAutorelease(FD, FName) ||
- isMakeCollectable(FD, FName);
- } else {
- if (FD->getDefinition()) {
- canEval = isTrustedReferenceCountImplementation(FD->getDefinition());
- hasTrustedImplementationAnnotation = canEval;
- }
- }
- }
-
- if (!canEval)
- return false;
-
- // Bind the return value.
- const LocationContext *LCtx = C.getLocationContext();
- SVal RetVal = state->getSVal(CE->getArg(0), LCtx);
- if (RetVal.isUnknown() ||
- (hasTrustedImplementationAnnotation && !ResultTy.isNull())) {
- // If the receiver is unknown or the function has
- // 'rc_ownership_trusted_implementation' annotate attribute, conjure a
- // return value.
- SValBuilder &SVB = C.getSValBuilder();
- RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount());
- }
- state = state->BindExpr(CE, LCtx, RetVal, false);
-
- // FIXME: This should not be necessary, but otherwise the argument seems to be
- // considered alive during the next statement.
- if (const MemRegion *ArgRegion = RetVal.getAsRegion()) {
- // Save the refcount status of the argument.
- SymbolRef Sym = RetVal.getAsLocSymbol();
- const RefVal *Binding = nullptr;
- if (Sym)
- Binding = getRefBinding(state, Sym);
-
- // Invalidate the argument region.
- state = state->invalidateRegions(
- ArgRegion, CE, C.blockCount(), LCtx,
- /*CausesPointerEscape*/ hasTrustedImplementationAnnotation);
-
- // Restore the refcount status of the argument.
- if (Binding)
- state = setRefBinding(state, Sym, *Binding);
- }
-
- C.addTransition(state);
- return true;
-}
-
-//===----------------------------------------------------------------------===//
-// Handle return statements.
-//===----------------------------------------------------------------------===//
-
-void RetainCountChecker::checkPreStmt(const ReturnStmt *S,
- CheckerContext &C) const {
-
- // Only adjust the reference count if this is the top-level call frame,
- // and not the result of inlining. In the future, we should do
- // better checking even for inlined calls, and see if they match
- // with their expected semantics (e.g., the method should return a retained
- // object, etc.).
- if (!C.inTopFrame())
- return;
-
- const Expr *RetE = S->getRetValue();
- if (!RetE)
- return;
-
- ProgramStateRef state = C.getState();
- SymbolRef Sym =
- state->getSValAsScalarOrLoc(RetE, C.getLocationContext()).getAsLocSymbol();
- if (!Sym)
- return;
-
- // Get the reference count binding (if any).
- const RefVal *T = getRefBinding(state, Sym);
- if (!T)
- return;
-
- // Change the reference count.
- RefVal X = *T;
-
- switch (X.getKind()) {
- case RefVal::Owned: {
- unsigned cnt = X.getCount();
- assert(cnt > 0);
- X.setCount(cnt - 1);
- X = X ^ RefVal::ReturnedOwned;
- break;
- }
-
- case RefVal::NotOwned: {
- unsigned cnt = X.getCount();
- if (cnt) {
- X.setCount(cnt - 1);
- X = X ^ RefVal::ReturnedOwned;
- }
- else {
- X = X ^ RefVal::ReturnedNotOwned;
- }
- break;
- }
-
- default:
- return;
- }
-
- // Update the binding.
- state = setRefBinding(state, Sym, X);
- ExplodedNode *Pred = C.addTransition(state);
-
- // At this point we have updated the state properly.
- // Everything after this is merely checking to see if the return value has
- // been over- or under-retained.
-
- // Did we cache out?
- if (!Pred)
- return;
-
- // Update the autorelease counts.
- static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease");
- state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X);
-
- // Did we cache out?
- if (!state)
- return;
-
- // Get the updated binding.
- T = getRefBinding(state, Sym);
- assert(T);
- X = *T;
-
- // Consult the summary of the enclosing method.
- RetainSummaryManager &Summaries = getSummaryManager(C);
- const Decl *CD = &Pred->getCodeDecl();
- RetEffect RE = RetEffect::MakeNoRet();
-
- // FIXME: What is the convention for blocks? Is there one?
- if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CD)) {
- const RetainSummary *Summ = Summaries.getMethodSummary(MD);
- RE = Summ->getRetEffect();
- } else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CD)) {
- if (!isa<CXXMethodDecl>(FD)) {
- const RetainSummary *Summ = Summaries.getFunctionSummary(FD);
- RE = Summ->getRetEffect();
- }
- }
-
- checkReturnWithRetEffect(S, C, Pred, RE, X, Sym, state);
-}
-
-void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
- CheckerContext &C,
- ExplodedNode *Pred,
- RetEffect RE, RefVal X,
- SymbolRef Sym,
- ProgramStateRef state) const {
- // HACK: Ignore retain-count issues on values accessed through ivars,
- // because of cases like this:
- // [_contentView retain];
- // [_contentView removeFromSuperview];
- // [self addSubview:_contentView]; // invalidates 'self'
- // [_contentView release];
- if (X.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
- return;
-
- // Any leaks or other errors?
- if (X.isReturnedOwned() && X.getCount() == 0) {
- if (RE.getKind() != RetEffect::NoRet) {
- bool hasError = false;
- if (C.isObjCGCEnabled() && RE.getObjKind() == RetEffect::ObjC) {
- // Things are more complicated with garbage collection. If the
- // returned object is suppose to be an Objective-C object, we have
- // a leak (as the caller expects a GC'ed object) because no
- // method should return ownership unless it returns a CF object.
- hasError = true;
- X = X ^ RefVal::ErrorGCLeakReturned;
- }
- else if (!RE.isOwned()) {
- // Either we are using GC and the returned object is a CF type
- // or we aren't using GC. In either case, we expect that the
- // enclosing method is expected to return ownership.
- hasError = true;
- X = X ^ RefVal::ErrorLeakReturned;
- }
-
- if (hasError) {
- // Generate an error node.
- state = setRefBinding(state, Sym, X);
-
- static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak");
- ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag);
- if (N) {
- const LangOptions &LOpts = C.getASTContext().getLangOpts();
- bool GCEnabled = C.isObjCGCEnabled();
- C.emitReport(std::unique_ptr<BugReport>(new CFRefLeakReport(
- *getLeakAtReturnBug(LOpts, GCEnabled), LOpts, GCEnabled,
- SummaryLog, N, Sym, C, IncludeAllocationLine)));
- }
- }
- }
- } else if (X.isReturnedNotOwned()) {
- if (RE.isOwned()) {
- if (X.getIvarAccessHistory() ==
- RefVal::IvarAccessHistory::AccessedDirectly) {
- // Assume the method was trying to transfer a +1 reference from a
- // strong ivar to the caller.
- state = setRefBinding(state, Sym,
- X.releaseViaIvar() ^ RefVal::ReturnedOwned);
- } else {
- // Trying to return a not owned object to a caller expecting an
- // owned object.
- state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned);
-
- static CheckerProgramPointTag
- ReturnNotOwnedTag(this, "ReturnNotOwnedForOwned");
-
- ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag);
- if (N) {
- if (!returnNotOwnedForOwned)
- returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned(this));
-
- C.emitReport(std::unique_ptr<BugReport>(new CFRefReport(
- *returnNotOwnedForOwned, C.getASTContext().getLangOpts(),
- C.isObjCGCEnabled(), SummaryLog, N, Sym)));
- }
- }
- }
- }
-}
-
-//===----------------------------------------------------------------------===//
-// Check various ways a symbol can be invalidated.
-//===----------------------------------------------------------------------===//
-
-void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S,
- CheckerContext &C) const {
- // Are we storing to something that causes the value to "escape"?
- bool escapes = true;
-
- // A value escapes in three possible cases (this may change):
- //
- // (1) we are binding to something that is not a memory region.
- // (2) we are binding to a memregion that does not have stack storage
- // (3) we are binding to a memregion with stack storage that the store
- // does not understand.
- ProgramStateRef state = C.getState();
-
- if (Optional<loc::MemRegionVal> regionLoc = loc.getAs<loc::MemRegionVal>()) {
- escapes = !regionLoc->getRegion()->hasStackStorage();
-
- if (!escapes) {
- // To test (3), generate a new state with the binding added. If it is
- // the same state, then it escapes (since the store cannot represent
- // the binding).
- // Do this only if we know that the store is not supposed to generate the
- // same state.
- SVal StoredVal = state->getSVal(regionLoc->getRegion());
- if (StoredVal != val)
- escapes = (state == (state->bindLoc(*regionLoc, val, C.getLocationContext())));
- }
- if (!escapes) {
- // Case 4: We do not currently model what happens when a symbol is
- // assigned to a struct field, so be conservative here and let the symbol
- // go. TODO: This could definitely be improved upon.
- escapes = !isa<VarRegion>(regionLoc->getRegion());
- }
- }
-
- // If we are storing the value into an auto function scope variable annotated
- // with (__attribute__((cleanup))), stop tracking the value to avoid leak
- // false positives.
- if (const VarRegion *LVR = dyn_cast_or_null<VarRegion>(loc.getAsRegion())) {
- const VarDecl *VD = LVR->getDecl();
- if (VD->hasAttr<CleanupAttr>()) {
- escapes = true;
- }
- }
-
- // If our store can represent the binding and we aren't storing to something
- // that doesn't have local storage then just return and have the simulation
- // state continue as is.
- if (!escapes)
- return;
-
- // Otherwise, find all symbols referenced by 'val' that we are tracking
- // and stop tracking them.
- state = state->scanReachableSymbols<StopTrackingCallback>(val).getState();
- C.addTransition(state);
-}
-
-ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state,
- SVal Cond,
- bool Assumption) const {
- // FIXME: We may add to the interface of evalAssume the list of symbols
- // whose assumptions have changed. For now we just iterate through the
- // bindings and check if any of the tracked symbols are NULL. This isn't
- // too bad since the number of symbols we will track in practice are
- // probably small and evalAssume is only called at branches and a few
- // other places.
- RefBindingsTy B = state->get<RefBindings>();
-
- if (B.isEmpty())
- return state;
-
- bool changed = false;
- RefBindingsTy::Factory &RefBFactory = state->get_context<RefBindings>();
-
- for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
- // Check if the symbol is null stop tracking the symbol.
- ConstraintManager &CMgr = state->getConstraintManager();
- ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey());
- if (AllocFailed.isConstrainedTrue()) {
- changed = true;
- B = RefBFactory.remove(B, I.getKey());
- }
- }
-
- if (changed)
- state = state->set<RefBindings>(B);
-
- return state;
-}
-
-ProgramStateRef
-RetainCountChecker::checkRegionChanges(ProgramStateRef state,
- const InvalidatedSymbols *invalidated,
- ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions,
- const LocationContext *LCtx,
- const CallEvent *Call) const {
- if (!invalidated)
- return state;
-
- llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols;
- for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(),
- E = ExplicitRegions.end(); I != E; ++I) {
- if (const SymbolicRegion *SR = (*I)->StripCasts()->getAs<SymbolicRegion>())
- WhitelistedSymbols.insert(SR->getSymbol());
- }
-
- for (InvalidatedSymbols::const_iterator I=invalidated->begin(),
- E = invalidated->end(); I!=E; ++I) {
- SymbolRef sym = *I;
- if (WhitelistedSymbols.count(sym))
- continue;
- // Remove any existing reference-count binding.
- state = removeRefBinding(state, sym);
- }
- return state;
-}
-
-//===----------------------------------------------------------------------===//
-// Handle dead symbols and end-of-path.
-//===----------------------------------------------------------------------===//
-
-ProgramStateRef
-RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state,
- ExplodedNode *Pred,
- const ProgramPointTag *Tag,
- CheckerContext &Ctx,
- SymbolRef Sym, RefVal V) const {
- unsigned ACnt = V.getAutoreleaseCount();
-
- // No autorelease counts? Nothing to be done.
- if (!ACnt)
- return state;
-
- assert(!Ctx.isObjCGCEnabled() && "Autorelease counts in GC mode?");
- unsigned Cnt = V.getCount();
-
- // FIXME: Handle sending 'autorelease' to already released object.
-
- if (V.getKind() == RefVal::ReturnedOwned)
- ++Cnt;
-
- // If we would over-release here, but we know the value came from an ivar,
- // assume it was a strong ivar that's just been relinquished.
- if (ACnt > Cnt &&
- V.getIvarAccessHistory() == RefVal::IvarAccessHistory::AccessedDirectly) {
- V = V.releaseViaIvar();
- --ACnt;
- }
-
- if (ACnt <= Cnt) {
- if (ACnt == Cnt) {
- V.clearCounts();
- if (V.getKind() == RefVal::ReturnedOwned)
- V = V ^ RefVal::ReturnedNotOwned;
- else
- V = V ^ RefVal::NotOwned;
- } else {
- V.setCount(V.getCount() - ACnt);
- V.setAutoreleaseCount(0);
- }
- return setRefBinding(state, Sym, V);
- }
-
- // HACK: Ignore retain-count issues on values accessed through ivars,
- // because of cases like this:
- // [_contentView retain];
- // [_contentView removeFromSuperview];
- // [self addSubview:_contentView]; // invalidates 'self'
- // [_contentView release];
- if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
- return state;
-
- // Woah! More autorelease counts then retain counts left.
- // Emit hard error.
- V = V ^ RefVal::ErrorOverAutorelease;
- state = setRefBinding(state, Sym, V);
-
- ExplodedNode *N = Ctx.generateSink(state, Pred, Tag);
- if (N) {
- SmallString<128> sbuf;
- llvm::raw_svector_ostream os(sbuf);
- os << "Object was autoreleased ";
- if (V.getAutoreleaseCount() > 1)
- os << V.getAutoreleaseCount() << " times but the object ";
- else
- os << "but ";
- os << "has a +" << V.getCount() << " retain count";
-
- if (!overAutorelease)
- overAutorelease.reset(new OverAutorelease(this));
-
- const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
- Ctx.emitReport(std::unique_ptr<BugReport>(
- new CFRefReport(*overAutorelease, LOpts, /* GCEnabled = */ false,
- SummaryLog, N, Sym, os.str())));
- }
-
- return nullptr;
-}
-
-ProgramStateRef
-RetainCountChecker::handleSymbolDeath(ProgramStateRef state,
- SymbolRef sid, RefVal V,
- SmallVectorImpl<SymbolRef> &Leaked) const {
- bool hasLeak;
-
- // HACK: Ignore retain-count issues on values accessed through ivars,
- // because of cases like this:
- // [_contentView retain];
- // [_contentView removeFromSuperview];
- // [self addSubview:_contentView]; // invalidates 'self'
- // [_contentView release];
- if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
- hasLeak = false;
- else if (V.isOwned())
- hasLeak = true;
- else if (V.isNotOwned() || V.isReturnedOwned())
- hasLeak = (V.getCount() > 0);
- else
- hasLeak = false;
-
- if (!hasLeak)
- return removeRefBinding(state, sid);
-
- Leaked.push_back(sid);
- return setRefBinding(state, sid, V ^ RefVal::ErrorLeak);
-}
-
-ExplodedNode *
-RetainCountChecker::processLeaks(ProgramStateRef state,
- SmallVectorImpl<SymbolRef> &Leaked,
- CheckerContext &Ctx,
- ExplodedNode *Pred) const {
- // Generate an intermediate node representing the leak point.
- ExplodedNode *N = Ctx.addTransition(state, Pred);
-
- if (N) {
- for (SmallVectorImpl<SymbolRef>::iterator
- I = Leaked.begin(), E = Leaked.end(); I != E; ++I) {
-
- const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
- bool GCEnabled = Ctx.isObjCGCEnabled();
- CFRefBug *BT = Pred ? getLeakWithinFunctionBug(LOpts, GCEnabled)
- : getLeakAtReturnBug(LOpts, GCEnabled);
- assert(BT && "BugType not initialized.");
-
- Ctx.emitReport(std::unique_ptr<BugReport>(
- new CFRefLeakReport(*BT, LOpts, GCEnabled, SummaryLog, N, *I, Ctx,
- IncludeAllocationLine)));
- }
- }
-
- return N;
-}
-
-void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const {
- if (!Ctx.inTopFrame())
- return;
-
- const LocationContext *LCtx = Ctx.getLocationContext();
- const FunctionDecl *FD = dyn_cast<FunctionDecl>(LCtx->getDecl());
-
- if (!FD || isTrustedReferenceCountImplementation(FD))
- return;
-
- ProgramStateRef state = Ctx.getState();
-
- const RetainSummary *FunctionSummary = getSummaryManager(Ctx).getFunctionSummary(FD);
- ArgEffects CalleeSideArgEffects = FunctionSummary->getArgEffects();
-
- for (unsigned idx = 0, e = FD->getNumParams(); idx != e; ++idx) {
- const ParmVarDecl *Param = FD->getParamDecl(idx);
- SymbolRef Sym = state->getSVal(state->getRegion(Param, LCtx)).getAsSymbol();
-
- QualType Ty = Param->getType();
- const ArgEffect *AE = CalleeSideArgEffects.lookup(idx);
- if (AE && *AE == DecRef && isGeneralizedObjectRef(Ty))
- state = setRefBinding(state, Sym, RefVal::makeOwned(RetEffect::ObjKind::Generalized, Ty));
- else if (isGeneralizedObjectRef(Ty))
- state = setRefBinding(state, Sym, RefVal::makeNotOwned(RetEffect::ObjKind::Generalized, Ty));
- }
-
- Ctx.addTransition(state);
-}
-
-void RetainCountChecker::checkEndFunction(const ReturnStmt *RS,
- CheckerContext &Ctx) const {
- ProgramStateRef state = Ctx.getState();
- RefBindingsTy B = state->get<RefBindings>();
- ExplodedNode *Pred = Ctx.getPredecessor();
-
- // Don't process anything within synthesized bodies.
- const LocationContext *LCtx = Pred->getLocationContext();
- if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) {
- assert(!LCtx->inTopFrame());
- return;
- }
-
- for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
- state = handleAutoreleaseCounts(state, Pred, /*Tag=*/nullptr, Ctx,
- I->first, I->second);
- if (!state)
- return;
- }
-
- // If the current LocationContext has a parent, don't check for leaks.
- // We will do that later.
- // FIXME: we should instead check for imbalances of the retain/releases,
- // and suggest annotations.
- if (LCtx->getParent())
- return;
-
- B = state->get<RefBindings>();
- SmallVector<SymbolRef, 10> Leaked;
-
- for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I)
- state = handleSymbolDeath(state, I->first, I->second, Leaked);
-
- processLeaks(state, Leaked, Ctx, Pred);
-}
-
-const ProgramPointTag *
-RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const {
- const CheckerProgramPointTag *&tag = DeadSymbolTags[sym];
- if (!tag) {
- SmallString<64> buf;
- llvm::raw_svector_ostream out(buf);
- out << "Dead Symbol : ";
- sym->dumpToStream(out);
- tag = new CheckerProgramPointTag(this, out.str());
- }
- return tag;
-}
-
-void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper,
- CheckerContext &C) const {
- ExplodedNode *Pred = C.getPredecessor();
-
- ProgramStateRef state = C.getState();
- RefBindingsTy B = state->get<RefBindings>();
- SmallVector<SymbolRef, 10> Leaked;
-
- // Update counts from autorelease pools
- for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
- E = SymReaper.dead_end(); I != E; ++I) {
- SymbolRef Sym = *I;
- if (const RefVal *T = B.lookup(Sym)){
- // Use the symbol as the tag.
- // FIXME: This might not be as unique as we would like.
- const ProgramPointTag *Tag = getDeadSymbolTag(Sym);
- state = handleAutoreleaseCounts(state, Pred, Tag, C, Sym, *T);
- if (!state)
- return;
-
- // Fetch the new reference count from the state, and use it to handle
- // this symbol.
- state = handleSymbolDeath(state, *I, *getRefBinding(state, Sym), Leaked);
- }
- }
-
- if (Leaked.empty()) {
- C.addTransition(state);
- return;
- }
-
- Pred = processLeaks(state, Leaked, C, Pred);
-
- // Did we cache out?
- if (!Pred)
- return;
-
- // Now generate a new node that nukes the old bindings.
- // The only bindings left at this point are the leaked symbols.
- RefBindingsTy::Factory &F = state->get_context<RefBindings>();
- B = state->get<RefBindings>();
-
- for (SmallVectorImpl<SymbolRef>::iterator I = Leaked.begin(),
- E = Leaked.end();
- I != E; ++I)
- B = F.remove(B, *I);
-
- state = state->set<RefBindings>(B);
- C.addTransition(state, Pred);
-}
-
-void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State,
- const char *NL, const char *Sep) const {
-
- RefBindingsTy B = State->get<RefBindings>();
-
- if (B.isEmpty())
- return;
-
- Out << Sep << NL;
-
- for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
- Out << I->first << " : ";
- I->second.print(Out);
- Out << NL;
- }
-}
-
-//===----------------------------------------------------------------------===//
-// Checker registration.
-//===----------------------------------------------------------------------===//
-
-void ento::registerRetainCountChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<RetainCountChecker>(Mgr.getAnalyzerOptions());
-}
-
-//===----------------------------------------------------------------------===//
-// Implementation of the CallEffects API.
-//===----------------------------------------------------------------------===//
-
-namespace clang {
-namespace ento {
-namespace objc_retain {
-
-// This is a bit gross, but it allows us to populate CallEffects without
-// creating a bunch of accessors. This kind is very localized, so the
-// damage of this macro is limited.
-#define createCallEffect(D, KIND)\
- ASTContext &Ctx = D->getASTContext();\
- LangOptions L = Ctx.getLangOpts();\
- RetainSummaryManager M(Ctx, L.GCOnly, L.ObjCAutoRefCount);\
- const RetainSummary *S = M.get ## KIND ## Summary(D);\
- CallEffects CE(S->getRetEffect());\
- CE.Receiver = S->getReceiverEffect();\
- unsigned N = D->param_size();\
- for (unsigned i = 0; i < N; ++i) {\
- CE.Args.push_back(S->getArg(i));\
- }
-
-CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) {
- createCallEffect(MD, Method);
- return CE;
-}
-
-CallEffects CallEffects::getEffect(const FunctionDecl *FD) {
- createCallEffect(FD, Function);
- return CE;
-}
-
-#undef createCallEffect
-
-} // end namespace objc_retain
-} // end namespace ento
-} // end namespace clang
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp
new file mode 100644
index 000000000000..0652af856643
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp
@@ -0,0 +1,1547 @@
+//==-- RetainCountChecker.cpp - Checks for leaks and other issues -*- 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 methods for RetainCountChecker, which implements
+// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
+//
+//===----------------------------------------------------------------------===//
+
+#include "RetainCountChecker.h"
+
+using namespace clang;
+using namespace ento;
+using namespace retaincountchecker;
+using llvm::StrInStrNoCase;
+
+REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal)
+
+namespace clang {
+namespace ento {
+namespace retaincountchecker {
+
+const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym) {
+ return State->get<RefBindings>(Sym);
+}
+
+ProgramStateRef setRefBinding(ProgramStateRef State, SymbolRef Sym,
+ RefVal Val) {
+ assert(Sym != nullptr);
+ return State->set<RefBindings>(Sym, Val);
+}
+
+ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) {
+ return State->remove<RefBindings>(Sym);
+}
+
+class UseAfterRelease : public RefCountBug {
+public:
+ UseAfterRelease(const CheckerBase *checker)
+ : RefCountBug(checker, "Use-after-release") {}
+
+ const char *getDescription() const override {
+ return "Reference-counted object is used after it is released";
+ }
+};
+
+class BadRelease : public RefCountBug {
+public:
+ BadRelease(const CheckerBase *checker) : RefCountBug(checker, "Bad release") {}
+
+ const char *getDescription() const override {
+ return "Incorrect decrement of the reference count of an object that is "
+ "not owned at this point by the caller";
+ }
+};
+
+class DeallocNotOwned : public RefCountBug {
+public:
+ DeallocNotOwned(const CheckerBase *checker)
+ : RefCountBug(checker, "-dealloc sent to non-exclusively owned object") {}
+
+ const char *getDescription() const override {
+ return "-dealloc sent to object that may be referenced elsewhere";
+ }
+};
+
+class OverAutorelease : public RefCountBug {
+public:
+ OverAutorelease(const CheckerBase *checker)
+ : RefCountBug(checker, "Object autoreleased too many times") {}
+
+ const char *getDescription() const override {
+ return "Object autoreleased too many times";
+ }
+};
+
+class ReturnedNotOwnedForOwned : public RefCountBug {
+public:
+ ReturnedNotOwnedForOwned(const CheckerBase *checker)
+ : RefCountBug(checker, "Method should return an owned object") {}
+
+ const char *getDescription() const override {
+ return "Object with a +0 retain count returned to caller where a +1 "
+ "(owning) retain count is expected";
+ }
+};
+
+class Leak : public RefCountBug {
+public:
+ Leak(const CheckerBase *checker, StringRef name) : RefCountBug(checker, name) {
+ // Leaks should not be reported if they are post-dominated by a sink.
+ setSuppressOnSink(true);
+ }
+
+ const char *getDescription() const override { return ""; }
+
+ bool isLeak() const override { return true; }
+};
+
+} // end namespace retaincountchecker
+} // end namespace ento
+} // end namespace clang
+
+void RefVal::print(raw_ostream &Out) const {
+ if (!T.isNull())
+ Out << "Tracked " << T.getAsString() << " | ";
+
+ switch (getKind()) {
+ default: llvm_unreachable("Invalid RefVal kind");
+ case Owned: {
+ Out << "Owned";
+ unsigned cnt = getCount();
+ if (cnt) Out << " (+ " << cnt << ")";
+ break;
+ }
+
+ case NotOwned: {
+ Out << "NotOwned";
+ unsigned cnt = getCount();
+ if (cnt) Out << " (+ " << cnt << ")";
+ break;
+ }
+
+ case ReturnedOwned: {
+ Out << "ReturnedOwned";
+ unsigned cnt = getCount();
+ if (cnt) Out << " (+ " << cnt << ")";
+ break;
+ }
+
+ case ReturnedNotOwned: {
+ Out << "ReturnedNotOwned";
+ unsigned cnt = getCount();
+ if (cnt) Out << " (+ " << cnt << ")";
+ break;
+ }
+
+ case Released:
+ Out << "Released";
+ break;
+
+ case ErrorDeallocNotOwned:
+ Out << "-dealloc (not-owned)";
+ break;
+
+ case ErrorLeak:
+ Out << "Leaked";
+ break;
+
+ case ErrorLeakReturned:
+ Out << "Leaked (Bad naming)";
+ break;
+
+ case ErrorUseAfterRelease:
+ Out << "Use-After-Release [ERROR]";
+ break;
+
+ case ErrorReleaseNotOwned:
+ Out << "Release of Not-Owned [ERROR]";
+ break;
+
+ case RefVal::ErrorOverAutorelease:
+ Out << "Over-autoreleased";
+ break;
+
+ case RefVal::ErrorReturnedNotOwned:
+ Out << "Non-owned object returned instead of owned";
+ break;
+ }
+
+ switch (getIvarAccessHistory()) {
+ case IvarAccessHistory::None:
+ break;
+ case IvarAccessHistory::AccessedDirectly:
+ Out << " [direct ivar access]";
+ break;
+ case IvarAccessHistory::ReleasedAfterDirectAccess:
+ Out << " [released after direct ivar access]";
+ }
+
+ if (ACnt) {
+ Out << " [autorelease -" << ACnt << ']';
+ }
+}
+
+namespace {
+class StopTrackingCallback final : public SymbolVisitor {
+ ProgramStateRef state;
+public:
+ StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {}
+ ProgramStateRef getState() const { return state; }
+
+ bool VisitSymbol(SymbolRef sym) override {
+ state = state->remove<RefBindings>(sym);
+ return true;
+ }
+};
+} // end anonymous namespace
+
+//===----------------------------------------------------------------------===//
+// Handle statements that may have an effect on refcounts.
+//===----------------------------------------------------------------------===//
+
+void RetainCountChecker::checkPostStmt(const BlockExpr *BE,
+ CheckerContext &C) const {
+
+ // Scan the BlockDecRefExprs for any object the retain count checker
+ // may be tracking.
+ if (!BE->getBlockDecl()->hasCaptures())
+ return;
+
+ ProgramStateRef state = C.getState();
+ auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion());
+
+ BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(),
+ E = R->referenced_vars_end();
+
+ if (I == E)
+ return;
+
+ // FIXME: For now we invalidate the tracking of all symbols passed to blocks
+ // via captured variables, even though captured variables result in a copy
+ // and in implicit increment/decrement of a retain count.
+ SmallVector<const MemRegion*, 10> Regions;
+ const LocationContext *LC = C.getLocationContext();
+ MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager();
+
+ for ( ; I != E; ++I) {
+ const VarRegion *VR = I.getCapturedRegion();
+ if (VR->getSuperRegion() == R) {
+ VR = MemMgr.getVarRegion(VR->getDecl(), LC);
+ }
+ Regions.push_back(VR);
+ }
+
+ state = state->scanReachableSymbols<StopTrackingCallback>(Regions).getState();
+ C.addTransition(state);
+}
+
+void RetainCountChecker::checkPostStmt(const CastExpr *CE,
+ CheckerContext &C) const {
+ const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE);
+ if (!BE)
+ return;
+
+ ArgEffect AE = ArgEffect(IncRef, ObjKind::ObjC);
+
+ switch (BE->getBridgeKind()) {
+ case OBC_Bridge:
+ // Do nothing.
+ return;
+ case OBC_BridgeRetained:
+ AE = AE.withKind(IncRef);
+ break;
+ case OBC_BridgeTransfer:
+ AE = AE.withKind(DecRefBridgedTransferred);
+ break;
+ }
+
+ ProgramStateRef state = C.getState();
+ SymbolRef Sym = C.getSVal(CE).getAsLocSymbol();
+ if (!Sym)
+ return;
+ const RefVal* T = getRefBinding(state, Sym);
+ if (!T)
+ return;
+
+ RefVal::Kind hasErr = (RefVal::Kind) 0;
+ state = updateSymbol(state, Sym, *T, AE, hasErr, C);
+
+ if (hasErr) {
+ // FIXME: If we get an error during a bridge cast, should we report it?
+ return;
+ }
+
+ C.addTransition(state);
+}
+
+void RetainCountChecker::processObjCLiterals(CheckerContext &C,
+ const Expr *Ex) const {
+ ProgramStateRef state = C.getState();
+ const ExplodedNode *pred = C.getPredecessor();
+ for (const Stmt *Child : Ex->children()) {
+ SVal V = pred->getSVal(Child);
+ if (SymbolRef sym = V.getAsSymbol())
+ if (const RefVal* T = getRefBinding(state, sym)) {
+ RefVal::Kind hasErr = (RefVal::Kind) 0;
+ state = updateSymbol(state, sym, *T,
+ ArgEffect(MayEscape, ObjKind::ObjC), hasErr, C);
+ if (hasErr) {
+ processNonLeakError(state, Child->getSourceRange(), hasErr, sym, C);
+ return;
+ }
+ }
+ }
+
+ // Return the object as autoreleased.
+ // RetEffect RE = RetEffect::MakeNotOwned(ObjKind::ObjC);
+ if (SymbolRef sym =
+ state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) {
+ QualType ResultTy = Ex->getType();
+ state = setRefBinding(state, sym,
+ RefVal::makeNotOwned(ObjKind::ObjC, ResultTy));
+ }
+
+ C.addTransition(state);
+}
+
+void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL,
+ CheckerContext &C) const {
+ // Apply the 'MayEscape' to all values.
+ processObjCLiterals(C, AL);
+}
+
+void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL,
+ CheckerContext &C) const {
+ // Apply the 'MayEscape' to all keys and values.
+ processObjCLiterals(C, DL);
+}
+
+void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex,
+ CheckerContext &C) const {
+ const ExplodedNode *Pred = C.getPredecessor();
+ ProgramStateRef State = Pred->getState();
+
+ if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) {
+ QualType ResultTy = Ex->getType();
+ State = setRefBinding(State, Sym,
+ RefVal::makeNotOwned(ObjKind::ObjC, ResultTy));
+ }
+
+ C.addTransition(State);
+}
+
+void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE,
+ CheckerContext &C) const {
+ Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>();
+ if (!IVarLoc)
+ return;
+
+ ProgramStateRef State = C.getState();
+ SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol();
+ if (!Sym || !dyn_cast_or_null<ObjCIvarRegion>(Sym->getOriginRegion()))
+ return;
+
+ // Accessing an ivar directly is unusual. If we've done that, be more
+ // forgiving about what the surrounding code is allowed to do.
+
+ QualType Ty = Sym->getType();
+ ObjKind Kind;
+ if (Ty->isObjCRetainableType())
+ Kind = ObjKind::ObjC;
+ else if (coreFoundation::isCFObjectRef(Ty))
+ Kind = ObjKind::CF;
+ else
+ return;
+
+ // If the value is already known to be nil, don't bother tracking it.
+ ConstraintManager &CMgr = State->getConstraintManager();
+ if (CMgr.isNull(State, Sym).isConstrainedTrue())
+ return;
+
+ if (const RefVal *RV = getRefBinding(State, Sym)) {
+ // If we've seen this symbol before, or we're only seeing it now because
+ // of something the analyzer has synthesized, don't do anything.
+ if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None ||
+ isSynthesizedAccessor(C.getStackFrame())) {
+ return;
+ }
+
+ // Note that this value has been loaded from an ivar.
+ C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess()));
+ return;
+ }
+
+ RefVal PlusZero = RefVal::makeNotOwned(Kind, Ty);
+
+ // In a synthesized accessor, the effective retain count is +0.
+ if (isSynthesizedAccessor(C.getStackFrame())) {
+ C.addTransition(setRefBinding(State, Sym, PlusZero));
+ return;
+ }
+
+ State = setRefBinding(State, Sym, PlusZero.withIvarAccess());
+ C.addTransition(State);
+}
+
+void RetainCountChecker::checkPostCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ RetainSummaryManager &Summaries = getSummaryManager(C);
+
+ // Leave null if no receiver.
+ QualType ReceiverType;
+ if (const auto *MC = dyn_cast<ObjCMethodCall>(&Call)) {
+ if (MC->isInstanceMessage()) {
+ SVal ReceiverV = MC->getReceiverSVal();
+ if (SymbolRef Sym = ReceiverV.getAsLocSymbol())
+ if (const RefVal *T = getRefBinding(C.getState(), Sym))
+ ReceiverType = T->getType();
+ }
+ }
+
+ const RetainSummary *Summ = Summaries.getSummary(Call, ReceiverType);
+
+ if (C.wasInlined) {
+ processSummaryOfInlined(*Summ, Call, C);
+ return;
+ }
+ checkSummary(*Summ, Call, C);
+}
+
+RefCountBug *
+RetainCountChecker::getLeakWithinFunctionBug(const LangOptions &LOpts) const {
+ if (!leakWithinFunction)
+ leakWithinFunction.reset(new Leak(this, "Leak"));
+ return leakWithinFunction.get();
+}
+
+RefCountBug *
+RetainCountChecker::getLeakAtReturnBug(const LangOptions &LOpts) const {
+ if (!leakAtReturn)
+ leakAtReturn.reset(new Leak(this, "Leak of returned object"));
+ return leakAtReturn.get();
+}
+
+/// GetReturnType - Used to get the return type of a message expression or
+/// function call with the intention of affixing that type to a tracked symbol.
+/// While the return type can be queried directly from RetEx, when
+/// invoking class methods we augment to the return type to be that of
+/// a pointer to the class (as opposed it just being id).
+// FIXME: We may be able to do this with related result types instead.
+// This function is probably overestimating.
+static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) {
+ QualType RetTy = RetE->getType();
+ // If RetE is not a message expression just return its type.
+ // If RetE is a message expression, return its types if it is something
+ /// more specific than id.
+ if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(RetE))
+ if (const ObjCObjectPointerType *PT = RetTy->getAs<ObjCObjectPointerType>())
+ if (PT->isObjCQualifiedIdType() || PT->isObjCIdType() ||
+ PT->isObjCClassType()) {
+ // At this point we know the return type of the message expression is
+ // id, id<...>, or Class. If we have an ObjCInterfaceDecl, we know this
+ // is a call to a class method whose type we can resolve. In such
+ // cases, promote the return type to XXX* (where XXX is the class).
+ const ObjCInterfaceDecl *D = ME->getReceiverInterface();
+ return !D ? RetTy :
+ Ctx.getObjCObjectPointerType(Ctx.getObjCInterfaceType(D));
+ }
+
+ return RetTy;
+}
+
+static Optional<RefVal> refValFromRetEffect(RetEffect RE,
+ QualType ResultTy) {
+ if (RE.isOwned()) {
+ return RefVal::makeOwned(RE.getObjKind(), ResultTy);
+ } else if (RE.notOwned()) {
+ return RefVal::makeNotOwned(RE.getObjKind(), ResultTy);
+ }
+
+ return None;
+}
+
+static bool isPointerToObject(QualType QT) {
+ QualType PT = QT->getPointeeType();
+ if (!PT.isNull())
+ if (PT->getAsCXXRecordDecl())
+ return true;
+ return false;
+}
+
+/// Whether the tracked value should be escaped on a given call.
+/// OSObjects are escaped when passed to void * / etc.
+static bool shouldEscapeOSArgumentOnCall(const CallEvent &CE, unsigned ArgIdx,
+ const RefVal *TrackedValue) {
+ if (TrackedValue->getObjKind() != ObjKind::OS)
+ return false;
+ if (ArgIdx >= CE.parameters().size())
+ return false;
+ return !isPointerToObject(CE.parameters()[ArgIdx]->getType());
+}
+
+// We don't always get the exact modeling of the function with regards to the
+// retain count checker even when the function is inlined. For example, we need
+// to stop tracking the symbols which were marked with StopTrackingHard.
+void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ,
+ const CallEvent &CallOrMsg,
+ CheckerContext &C) const {
+ ProgramStateRef state = C.getState();
+
+ // Evaluate the effect of the arguments.
+ for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
+ SVal V = CallOrMsg.getArgSVal(idx);
+
+ if (SymbolRef Sym = V.getAsLocSymbol()) {
+ bool ShouldRemoveBinding = Summ.getArg(idx).getKind() == StopTrackingHard;
+ if (const RefVal *T = getRefBinding(state, Sym))
+ if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
+ ShouldRemoveBinding = true;
+
+ if (ShouldRemoveBinding)
+ state = removeRefBinding(state, Sym);
+ }
+ }
+
+ // Evaluate the effect on the message receiver.
+ if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) {
+ if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
+ if (Summ.getReceiverEffect().getKind() == StopTrackingHard) {
+ state = removeRefBinding(state, Sym);
+ }
+ }
+ }
+
+ // Consult the summary for the return value.
+ RetEffect RE = Summ.getRetEffect();
+
+ if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) {
+ if (RE.getKind() == RetEffect::NoRetHard)
+ state = removeRefBinding(state, Sym);
+ }
+
+ C.addTransition(state);
+}
+
+static bool shouldEscapeRegion(const MemRegion *R) {
+
+ // We do not currently model what happens when a symbol is
+ // assigned to a struct field, so be conservative here and let the symbol
+ // go. TODO: This could definitely be improved upon.
+ return !R->hasStackStorage() || !isa<VarRegion>(R);
+}
+
+static SmallVector<ProgramStateRef, 2>
+updateOutParameters(ProgramStateRef State, const RetainSummary &Summ,
+ const CallEvent &CE) {
+
+ SVal L = CE.getReturnValue();
+
+ // Splitting is required to support out parameters,
+ // as out parameters might be created only on the "success" branch.
+ // We want to avoid eagerly splitting unless out parameters are actually
+ // needed.
+ bool SplitNecessary = false;
+ for (auto &P : Summ.getArgEffects())
+ if (P.second.getKind() == RetainedOutParameterOnNonZero ||
+ P.second.getKind() == RetainedOutParameterOnZero)
+ SplitNecessary = true;
+
+ ProgramStateRef AssumeNonZeroReturn = State;
+ ProgramStateRef AssumeZeroReturn = State;
+
+ if (SplitNecessary) {
+ if (auto DL = L.getAs<DefinedOrUnknownSVal>()) {
+ AssumeNonZeroReturn = AssumeNonZeroReturn->assume(*DL, true);
+ AssumeZeroReturn = AssumeZeroReturn->assume(*DL, false);
+ }
+ }
+
+ for (unsigned idx = 0, e = CE.getNumArgs(); idx != e; ++idx) {
+ SVal ArgVal = CE.getArgSVal(idx);
+ ArgEffect AE = Summ.getArg(idx);
+
+ auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion());
+ if (!ArgRegion)
+ continue;
+
+ QualType PointeeTy = ArgRegion->getValueType();
+ SVal PointeeVal = State->getSVal(ArgRegion);
+ SymbolRef Pointee = PointeeVal.getAsLocSymbol();
+ if (!Pointee)
+ continue;
+
+ if (shouldEscapeRegion(ArgRegion))
+ continue;
+
+ auto makeNotOwnedParameter = [&](ProgramStateRef St) {
+ return setRefBinding(St, Pointee,
+ RefVal::makeNotOwned(AE.getObjKind(), PointeeTy));
+ };
+ auto makeOwnedParameter = [&](ProgramStateRef St) {
+ return setRefBinding(St, Pointee,
+ RefVal::makeOwned(ObjKind::OS, PointeeTy));
+ };
+
+ switch (AE.getKind()) {
+ case UnretainedOutParameter:
+ AssumeNonZeroReturn = makeNotOwnedParameter(AssumeNonZeroReturn);
+ AssumeZeroReturn = makeNotOwnedParameter(AssumeZeroReturn);
+ break;
+ case RetainedOutParameter:
+ AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
+ AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
+ break;
+ case RetainedOutParameterOnNonZero:
+ AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn);
+ break;
+ case RetainedOutParameterOnZero:
+ AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (SplitNecessary) {
+ return {AssumeNonZeroReturn, AssumeZeroReturn};
+ } else {
+ assert(AssumeZeroReturn == AssumeNonZeroReturn);
+ return {AssumeZeroReturn};
+ }
+}
+
+void RetainCountChecker::checkSummary(const RetainSummary &Summ,
+ const CallEvent &CallOrMsg,
+ CheckerContext &C) const {
+ ProgramStateRef state = C.getState();
+
+ // Evaluate the effect of the arguments.
+ RefVal::Kind hasErr = (RefVal::Kind) 0;
+ SourceRange ErrorRange;
+ SymbolRef ErrorSym = nullptr;
+
+ // Helper tag for providing diagnostics: indicate whether dealloc was sent
+ // at this location.
+ static CheckerProgramPointTag DeallocSentTag(this, DeallocTagDescription);
+ bool DeallocSent = false;
+
+ for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) {
+ SVal V = CallOrMsg.getArgSVal(idx);
+
+ ArgEffect Effect = Summ.getArg(idx);
+ if (SymbolRef Sym = V.getAsLocSymbol()) {
+ if (const RefVal *T = getRefBinding(state, Sym)) {
+
+ if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T))
+ Effect = ArgEffect(StopTrackingHard, ObjKind::OS);
+
+ state = updateSymbol(state, Sym, *T, Effect, hasErr, C);
+ if (hasErr) {
+ ErrorRange = CallOrMsg.getArgSourceRange(idx);
+ ErrorSym = Sym;
+ break;
+ } else if (Effect.getKind() == Dealloc) {
+ DeallocSent = true;
+ }
+ }
+ }
+ }
+
+ // Evaluate the effect on the message receiver / `this` argument.
+ bool ReceiverIsTracked = false;
+ if (!hasErr) {
+ if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) {
+ if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) {
+ if (const RefVal *T = getRefBinding(state, Sym)) {
+ ReceiverIsTracked = true;
+ state = updateSymbol(state, Sym, *T,
+ Summ.getReceiverEffect(), hasErr, C);
+ if (hasErr) {
+ ErrorRange = MsgInvocation->getOriginExpr()->getReceiverRange();
+ ErrorSym = Sym;
+ } else if (Summ.getReceiverEffect().getKind() == Dealloc) {
+ DeallocSent = true;
+ }
+ }
+ }
+ } else if (const auto *MCall = dyn_cast<CXXMemberCall>(&CallOrMsg)) {
+ if (SymbolRef Sym = MCall->getCXXThisVal().getAsLocSymbol()) {
+ if (const RefVal *T = getRefBinding(state, Sym)) {
+ state = updateSymbol(state, Sym, *T, Summ.getThisEffect(),
+ hasErr, C);
+ if (hasErr) {
+ ErrorRange = MCall->getOriginExpr()->getSourceRange();
+ ErrorSym = Sym;
+ }
+ }
+ }
+ }
+ }
+
+ // Process any errors.
+ if (hasErr) {
+ processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C);
+ return;
+ }
+
+ // Consult the summary for the return value.
+ RetEffect RE = Summ.getRetEffect();
+
+ if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) {
+ if (ReceiverIsTracked)
+ RE = getSummaryManager(C).getObjAllocRetEffect();
+ else
+ RE = RetEffect::MakeNoRet();
+ }
+
+ if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) {
+ QualType ResultTy = CallOrMsg.getResultType();
+ if (RE.notOwned()) {
+ const Expr *Ex = CallOrMsg.getOriginExpr();
+ assert(Ex);
+ ResultTy = GetReturnType(Ex, C.getASTContext());
+ }
+ if (Optional<RefVal> updatedRefVal = refValFromRetEffect(RE, ResultTy))
+ state = setRefBinding(state, Sym, *updatedRefVal);
+ }
+
+ SmallVector<ProgramStateRef, 2> Out =
+ updateOutParameters(state, Summ, CallOrMsg);
+
+ for (ProgramStateRef St : Out) {
+ if (DeallocSent) {
+ C.addTransition(St, C.getPredecessor(), &DeallocSentTag);
+ } else {
+ C.addTransition(St);
+ }
+ }
+}
+
+ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state,
+ SymbolRef sym, RefVal V,
+ ArgEffect AE,
+ RefVal::Kind &hasErr,
+ CheckerContext &C) const {
+ bool IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount;
+ if (AE.getObjKind() == ObjKind::ObjC && IgnoreRetainMsg) {
+ switch (AE.getKind()) {
+ default:
+ break;
+ case IncRef:
+ AE = AE.withKind(DoNothing);
+ break;
+ case DecRef:
+ AE = AE.withKind(DoNothing);
+ break;
+ case DecRefAndStopTrackingHard:
+ AE = AE.withKind(StopTracking);
+ break;
+ }
+ }
+
+ // Handle all use-after-releases.
+ if (V.getKind() == RefVal::Released) {
+ V = V ^ RefVal::ErrorUseAfterRelease;
+ hasErr = V.getKind();
+ return setRefBinding(state, sym, V);
+ }
+
+ switch (AE.getKind()) {
+ case UnretainedOutParameter:
+ case RetainedOutParameter:
+ case RetainedOutParameterOnZero:
+ case RetainedOutParameterOnNonZero:
+ llvm_unreachable("Applies to pointer-to-pointer parameters, which should "
+ "not have ref state.");
+
+ case Dealloc: // NB. we only need to add a note in a non-error case.
+ switch (V.getKind()) {
+ default:
+ llvm_unreachable("Invalid RefVal state for an explicit dealloc.");
+ case RefVal::Owned:
+ // The object immediately transitions to the released state.
+ V = V ^ RefVal::Released;
+ V.clearCounts();
+ return setRefBinding(state, sym, V);
+ case RefVal::NotOwned:
+ V = V ^ RefVal::ErrorDeallocNotOwned;
+ hasErr = V.getKind();
+ break;
+ }
+ break;
+
+ case MayEscape:
+ if (V.getKind() == RefVal::Owned) {
+ V = V ^ RefVal::NotOwned;
+ break;
+ }
+
+ LLVM_FALLTHROUGH;
+
+ case DoNothing:
+ return state;
+
+ case Autorelease:
+ // Update the autorelease counts.
+ V = V.autorelease();
+ break;
+
+ case StopTracking:
+ case StopTrackingHard:
+ return removeRefBinding(state, sym);
+
+ case IncRef:
+ switch (V.getKind()) {
+ default:
+ llvm_unreachable("Invalid RefVal state for a retain.");
+ case RefVal::Owned:
+ case RefVal::NotOwned:
+ V = V + 1;
+ break;
+ }
+ break;
+
+ case DecRef:
+ case DecRefBridgedTransferred:
+ case DecRefAndStopTrackingHard:
+ switch (V.getKind()) {
+ default:
+ // case 'RefVal::Released' handled above.
+ llvm_unreachable("Invalid RefVal state for a release.");
+
+ case RefVal::Owned:
+ assert(V.getCount() > 0);
+ if (V.getCount() == 1) {
+ if (AE.getKind() == DecRefBridgedTransferred ||
+ V.getIvarAccessHistory() ==
+ RefVal::IvarAccessHistory::AccessedDirectly)
+ V = V ^ RefVal::NotOwned;
+ else
+ V = V ^ RefVal::Released;
+ } else if (AE.getKind() == DecRefAndStopTrackingHard) {
+ return removeRefBinding(state, sym);
+ }
+
+ V = V - 1;
+ break;
+
+ case RefVal::NotOwned:
+ if (V.getCount() > 0) {
+ if (AE.getKind() == DecRefAndStopTrackingHard)
+ return removeRefBinding(state, sym);
+ V = V - 1;
+ } else if (V.getIvarAccessHistory() ==
+ RefVal::IvarAccessHistory::AccessedDirectly) {
+ // Assume that the instance variable was holding on the object at
+ // +1, and we just didn't know.
+ if (AE.getKind() == DecRefAndStopTrackingHard)
+ return removeRefBinding(state, sym);
+ V = V.releaseViaIvar() ^ RefVal::Released;
+ } else {
+ V = V ^ RefVal::ErrorReleaseNotOwned;
+ hasErr = V.getKind();
+ }
+ break;
+ }
+ break;
+ }
+ return setRefBinding(state, sym, V);
+}
+
+void RetainCountChecker::processNonLeakError(ProgramStateRef St,
+ SourceRange ErrorRange,
+ RefVal::Kind ErrorKind,
+ SymbolRef Sym,
+ CheckerContext &C) const {
+ // HACK: Ignore retain-count issues on values accessed through ivars,
+ // because of cases like this:
+ // [_contentView retain];
+ // [_contentView removeFromSuperview];
+ // [self addSubview:_contentView]; // invalidates 'self'
+ // [_contentView release];
+ if (const RefVal *RV = getRefBinding(St, Sym))
+ if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
+ return;
+
+ ExplodedNode *N = C.generateErrorNode(St);
+ if (!N)
+ return;
+
+ RefCountBug *BT;
+ switch (ErrorKind) {
+ default:
+ llvm_unreachable("Unhandled error.");
+ case RefVal::ErrorUseAfterRelease:
+ if (!useAfterRelease)
+ useAfterRelease.reset(new UseAfterRelease(this));
+ BT = useAfterRelease.get();
+ break;
+ case RefVal::ErrorReleaseNotOwned:
+ if (!releaseNotOwned)
+ releaseNotOwned.reset(new BadRelease(this));
+ BT = releaseNotOwned.get();
+ break;
+ case RefVal::ErrorDeallocNotOwned:
+ if (!deallocNotOwned)
+ deallocNotOwned.reset(new DeallocNotOwned(this));
+ BT = deallocNotOwned.get();
+ break;
+ }
+
+ assert(BT);
+ auto report = llvm::make_unique<RefCountReport>(
+ *BT, C.getASTContext().getLangOpts(), N, Sym);
+ report->addRange(ErrorRange);
+ C.emitReport(std::move(report));
+}
+
+//===----------------------------------------------------------------------===//
+// Handle the return values of retain-count-related functions.
+//===----------------------------------------------------------------------===//
+
+bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const {
+ // Get the callee. We're only interested in simple C functions.
+ ProgramStateRef state = C.getState();
+ const FunctionDecl *FD = C.getCalleeDecl(CE);
+ if (!FD)
+ return false;
+
+ RetainSummaryManager &SmrMgr = getSummaryManager(C);
+ QualType ResultTy = CE->getCallReturnType(C.getASTContext());
+
+ // See if the function has 'rc_ownership_trusted_implementation'
+ // annotate attribute. If it does, we will not inline it.
+ bool hasTrustedImplementationAnnotation = false;
+
+ const LocationContext *LCtx = C.getLocationContext();
+
+ using BehaviorSummary = RetainSummaryManager::BehaviorSummary;
+ Optional<BehaviorSummary> BSmr =
+ SmrMgr.canEval(CE, FD, hasTrustedImplementationAnnotation);
+
+ // See if it's one of the specific functions we know how to eval.
+ if (!BSmr)
+ return false;
+
+ // Bind the return value.
+ if (BSmr == BehaviorSummary::Identity ||
+ BSmr == BehaviorSummary::IdentityOrZero) {
+ SVal RetVal = state->getSVal(CE->getArg(0), LCtx);
+
+ // If the receiver is unknown or the function has
+ // 'rc_ownership_trusted_implementation' annotate attribute, conjure a
+ // return value.
+ if (RetVal.isUnknown() ||
+ (hasTrustedImplementationAnnotation && !ResultTy.isNull())) {
+ SValBuilder &SVB = C.getSValBuilder();
+ RetVal =
+ SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount());
+ }
+ state = state->BindExpr(CE, LCtx, RetVal, /*Invalidate=*/false);
+
+ if (BSmr == BehaviorSummary::IdentityOrZero) {
+ // Add a branch where the output is zero.
+ ProgramStateRef NullOutputState = C.getState();
+
+ // Assume that output is zero on the other branch.
+ NullOutputState = NullOutputState->BindExpr(
+ CE, LCtx, C.getSValBuilder().makeNull(), /*Invalidate=*/false);
+
+ C.addTransition(NullOutputState);
+
+ // And on the original branch assume that both input and
+ // output are non-zero.
+ if (auto L = RetVal.getAs<DefinedOrUnknownSVal>())
+ state = state->assume(*L, /*Assumption=*/true);
+
+ }
+ }
+
+ C.addTransition(state);
+ return true;
+}
+
+ExplodedNode * RetainCountChecker::processReturn(const ReturnStmt *S,
+ CheckerContext &C) const {
+ ExplodedNode *Pred = C.getPredecessor();
+
+ // Only adjust the reference count if this is the top-level call frame,
+ // and not the result of inlining. In the future, we should do
+ // better checking even for inlined calls, and see if they match
+ // with their expected semantics (e.g., the method should return a retained
+ // object, etc.).
+ if (!C.inTopFrame())
+ return Pred;
+
+ if (!S)
+ return Pred;
+
+ const Expr *RetE = S->getRetValue();
+ if (!RetE)
+ return Pred;
+
+ ProgramStateRef state = C.getState();
+ SymbolRef Sym =
+ state->getSValAsScalarOrLoc(RetE, C.getLocationContext()).getAsLocSymbol();
+ if (!Sym)
+ return Pred;
+
+ // Get the reference count binding (if any).
+ const RefVal *T = getRefBinding(state, Sym);
+ if (!T)
+ return Pred;
+
+ // Change the reference count.
+ RefVal X = *T;
+
+ switch (X.getKind()) {
+ case RefVal::Owned: {
+ unsigned cnt = X.getCount();
+ assert(cnt > 0);
+ X.setCount(cnt - 1);
+ X = X ^ RefVal::ReturnedOwned;
+ break;
+ }
+
+ case RefVal::NotOwned: {
+ unsigned cnt = X.getCount();
+ if (cnt) {
+ X.setCount(cnt - 1);
+ X = X ^ RefVal::ReturnedOwned;
+ } else {
+ X = X ^ RefVal::ReturnedNotOwned;
+ }
+ break;
+ }
+
+ default:
+ return Pred;
+ }
+
+ // Update the binding.
+ state = setRefBinding(state, Sym, X);
+ Pred = C.addTransition(state);
+
+ // At this point we have updated the state properly.
+ // Everything after this is merely checking to see if the return value has
+ // been over- or under-retained.
+
+ // Did we cache out?
+ if (!Pred)
+ return nullptr;
+
+ // Update the autorelease counts.
+ static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease");
+ state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X, S);
+
+ // Have we generated a sink node?
+ if (!state)
+ return nullptr;
+
+ // Get the updated binding.
+ T = getRefBinding(state, Sym);
+ assert(T);
+ X = *T;
+
+ // Consult the summary of the enclosing method.
+ RetainSummaryManager &Summaries = getSummaryManager(C);
+ const Decl *CD = &Pred->getCodeDecl();
+ RetEffect RE = RetEffect::MakeNoRet();
+
+ // FIXME: What is the convention for blocks? Is there one?
+ if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CD)) {
+ const RetainSummary *Summ = Summaries.getMethodSummary(MD);
+ RE = Summ->getRetEffect();
+ } else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CD)) {
+ if (!isa<CXXMethodDecl>(FD)) {
+ const RetainSummary *Summ = Summaries.getFunctionSummary(FD);
+ RE = Summ->getRetEffect();
+ }
+ }
+
+ return checkReturnWithRetEffect(S, C, Pred, RE, X, Sym, state);
+}
+
+ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S,
+ CheckerContext &C,
+ ExplodedNode *Pred,
+ RetEffect RE, RefVal X,
+ SymbolRef Sym,
+ ProgramStateRef state) const {
+ // HACK: Ignore retain-count issues on values accessed through ivars,
+ // because of cases like this:
+ // [_contentView retain];
+ // [_contentView removeFromSuperview];
+ // [self addSubview:_contentView]; // invalidates 'self'
+ // [_contentView release];
+ if (X.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
+ return Pred;
+
+ // Any leaks or other errors?
+ if (X.isReturnedOwned() && X.getCount() == 0) {
+ if (RE.getKind() != RetEffect::NoRet) {
+ if (!RE.isOwned()) {
+
+ // The returning type is a CF, we expect the enclosing method should
+ // return ownership.
+ X = X ^ RefVal::ErrorLeakReturned;
+
+ // Generate an error node.
+ state = setRefBinding(state, Sym, X);
+
+ static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak");
+ ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag);
+ if (N) {
+ const LangOptions &LOpts = C.getASTContext().getLangOpts();
+ auto R = llvm::make_unique<RefLeakReport>(
+ *getLeakAtReturnBug(LOpts), LOpts, N, Sym, C);
+ C.emitReport(std::move(R));
+ }
+ return N;
+ }
+ }
+ } else if (X.isReturnedNotOwned()) {
+ if (RE.isOwned()) {
+ if (X.getIvarAccessHistory() ==
+ RefVal::IvarAccessHistory::AccessedDirectly) {
+ // Assume the method was trying to transfer a +1 reference from a
+ // strong ivar to the caller.
+ state = setRefBinding(state, Sym,
+ X.releaseViaIvar() ^ RefVal::ReturnedOwned);
+ } else {
+ // Trying to return a not owned object to a caller expecting an
+ // owned object.
+ state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned);
+
+ static CheckerProgramPointTag
+ ReturnNotOwnedTag(this, "ReturnNotOwnedForOwned");
+
+ ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag);
+ if (N) {
+ if (!returnNotOwnedForOwned)
+ returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned(this));
+
+ auto R = llvm::make_unique<RefCountReport>(
+ *returnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym);
+ C.emitReport(std::move(R));
+ }
+ return N;
+ }
+ }
+ }
+ return Pred;
+}
+
+//===----------------------------------------------------------------------===//
+// Check various ways a symbol can be invalidated.
+//===----------------------------------------------------------------------===//
+
+void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S,
+ CheckerContext &C) const {
+ // Are we storing to something that causes the value to "escape"?
+ bool escapes = true;
+
+ // A value escapes in three possible cases (this may change):
+ //
+ // (1) we are binding to something that is not a memory region.
+ // (2) we are binding to a memregion that does not have stack storage
+ ProgramStateRef state = C.getState();
+
+ if (auto regionLoc = loc.getAs<loc::MemRegionVal>()) {
+ escapes = shouldEscapeRegion(regionLoc->getRegion());
+ }
+
+ // If we are storing the value into an auto function scope variable annotated
+ // with (__attribute__((cleanup))), stop tracking the value to avoid leak
+ // false positives.
+ if (const auto *LVR = dyn_cast_or_null<VarRegion>(loc.getAsRegion())) {
+ const VarDecl *VD = LVR->getDecl();
+ if (VD->hasAttr<CleanupAttr>()) {
+ escapes = true;
+ }
+ }
+
+ // If our store can represent the binding and we aren't storing to something
+ // that doesn't have local storage then just return and have the simulation
+ // state continue as is.
+ if (!escapes)
+ return;
+
+ // Otherwise, find all symbols referenced by 'val' that we are tracking
+ // and stop tracking them.
+ state = state->scanReachableSymbols<StopTrackingCallback>(val).getState();
+ C.addTransition(state);
+}
+
+ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state,
+ SVal Cond,
+ bool Assumption) const {
+ // FIXME: We may add to the interface of evalAssume the list of symbols
+ // whose assumptions have changed. For now we just iterate through the
+ // bindings and check if any of the tracked symbols are NULL. This isn't
+ // too bad since the number of symbols we will track in practice are
+ // probably small and evalAssume is only called at branches and a few
+ // other places.
+ RefBindingsTy B = state->get<RefBindings>();
+
+ if (B.isEmpty())
+ return state;
+
+ bool changed = false;
+ RefBindingsTy::Factory &RefBFactory = state->get_context<RefBindings>();
+
+ for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
+ // Check if the symbol is null stop tracking the symbol.
+ ConstraintManager &CMgr = state->getConstraintManager();
+ ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey());
+ if (AllocFailed.isConstrainedTrue()) {
+ changed = true;
+ B = RefBFactory.remove(B, I.getKey());
+ }
+ }
+
+ if (changed)
+ state = state->set<RefBindings>(B);
+
+ return state;
+}
+
+ProgramStateRef
+RetainCountChecker::checkRegionChanges(ProgramStateRef state,
+ const InvalidatedSymbols *invalidated,
+ ArrayRef<const MemRegion *> ExplicitRegions,
+ ArrayRef<const MemRegion *> Regions,
+ const LocationContext *LCtx,
+ const CallEvent *Call) const {
+ if (!invalidated)
+ return state;
+
+ llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols;
+ for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(),
+ E = ExplicitRegions.end(); I != E; ++I) {
+ if (const SymbolicRegion *SR = (*I)->StripCasts()->getAs<SymbolicRegion>())
+ WhitelistedSymbols.insert(SR->getSymbol());
+ }
+
+ for (SymbolRef sym :
+ llvm::make_range(invalidated->begin(), invalidated->end())) {
+ if (WhitelistedSymbols.count(sym))
+ continue;
+ // Remove any existing reference-count binding.
+ state = removeRefBinding(state, sym);
+ }
+ return state;
+}
+
+ProgramStateRef
+RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state,
+ ExplodedNode *Pred,
+ const ProgramPointTag *Tag,
+ CheckerContext &Ctx,
+ SymbolRef Sym,
+ RefVal V,
+ const ReturnStmt *S) const {
+ unsigned ACnt = V.getAutoreleaseCount();
+
+ // No autorelease counts? Nothing to be done.
+ if (!ACnt)
+ return state;
+
+ unsigned Cnt = V.getCount();
+
+ // FIXME: Handle sending 'autorelease' to already released object.
+
+ if (V.getKind() == RefVal::ReturnedOwned)
+ ++Cnt;
+
+ // If we would over-release here, but we know the value came from an ivar,
+ // assume it was a strong ivar that's just been relinquished.
+ if (ACnt > Cnt &&
+ V.getIvarAccessHistory() == RefVal::IvarAccessHistory::AccessedDirectly) {
+ V = V.releaseViaIvar();
+ --ACnt;
+ }
+
+ if (ACnt <= Cnt) {
+ if (ACnt == Cnt) {
+ V.clearCounts();
+ if (V.getKind() == RefVal::ReturnedOwned) {
+ V = V ^ RefVal::ReturnedNotOwned;
+ } else {
+ V = V ^ RefVal::NotOwned;
+ }
+ } else {
+ V.setCount(V.getCount() - ACnt);
+ V.setAutoreleaseCount(0);
+ }
+ return setRefBinding(state, Sym, V);
+ }
+
+ // HACK: Ignore retain-count issues on values accessed through ivars,
+ // because of cases like this:
+ // [_contentView retain];
+ // [_contentView removeFromSuperview];
+ // [self addSubview:_contentView]; // invalidates 'self'
+ // [_contentView release];
+ if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
+ return state;
+
+ // Woah! More autorelease counts then retain counts left.
+ // Emit hard error.
+ V = V ^ RefVal::ErrorOverAutorelease;
+ state = setRefBinding(state, Sym, V);
+
+ ExplodedNode *N = Ctx.generateSink(state, Pred, Tag);
+ if (N) {
+ SmallString<128> sbuf;
+ llvm::raw_svector_ostream os(sbuf);
+ os << "Object was autoreleased ";
+ if (V.getAutoreleaseCount() > 1)
+ os << V.getAutoreleaseCount() << " times but the object ";
+ else
+ os << "but ";
+ os << "has a +" << V.getCount() << " retain count";
+
+ if (!overAutorelease)
+ overAutorelease.reset(new OverAutorelease(this));
+
+ const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
+ auto R = llvm::make_unique<RefCountReport>(*overAutorelease, LOpts, N, Sym,
+ os.str());
+ Ctx.emitReport(std::move(R));
+ }
+
+ return nullptr;
+}
+
+ProgramStateRef
+RetainCountChecker::handleSymbolDeath(ProgramStateRef state,
+ SymbolRef sid, RefVal V,
+ SmallVectorImpl<SymbolRef> &Leaked) const {
+ bool hasLeak;
+
+ // HACK: Ignore retain-count issues on values accessed through ivars,
+ // because of cases like this:
+ // [_contentView retain];
+ // [_contentView removeFromSuperview];
+ // [self addSubview:_contentView]; // invalidates 'self'
+ // [_contentView release];
+ if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None)
+ hasLeak = false;
+ else if (V.isOwned())
+ hasLeak = true;
+ else if (V.isNotOwned() || V.isReturnedOwned())
+ hasLeak = (V.getCount() > 0);
+ else
+ hasLeak = false;
+
+ if (!hasLeak)
+ return removeRefBinding(state, sid);
+
+ Leaked.push_back(sid);
+ return setRefBinding(state, sid, V ^ RefVal::ErrorLeak);
+}
+
+ExplodedNode *
+RetainCountChecker::processLeaks(ProgramStateRef state,
+ SmallVectorImpl<SymbolRef> &Leaked,
+ CheckerContext &Ctx,
+ ExplodedNode *Pred) const {
+ // Generate an intermediate node representing the leak point.
+ ExplodedNode *N = Ctx.addTransition(state, Pred);
+
+ if (N) {
+ for (SmallVectorImpl<SymbolRef>::iterator
+ I = Leaked.begin(), E = Leaked.end(); I != E; ++I) {
+
+ const LangOptions &LOpts = Ctx.getASTContext().getLangOpts();
+ RefCountBug *BT = Pred ? getLeakWithinFunctionBug(LOpts)
+ : getLeakAtReturnBug(LOpts);
+ assert(BT && "BugType not initialized.");
+
+ Ctx.emitReport(
+ llvm::make_unique<RefLeakReport>(*BT, LOpts, N, *I, Ctx));
+ }
+ }
+
+ return N;
+}
+
+static bool isISLObjectRef(QualType Ty) {
+ return StringRef(Ty.getAsString()).startswith("isl_");
+}
+
+void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const {
+ if (!Ctx.inTopFrame())
+ return;
+
+ RetainSummaryManager &SmrMgr = getSummaryManager(Ctx);
+ const LocationContext *LCtx = Ctx.getLocationContext();
+ const FunctionDecl *FD = dyn_cast<FunctionDecl>(LCtx->getDecl());
+
+ if (!FD || SmrMgr.isTrustedReferenceCountImplementation(FD))
+ return;
+
+ ProgramStateRef state = Ctx.getState();
+ const RetainSummary *FunctionSummary = SmrMgr.getFunctionSummary(FD);
+ ArgEffects CalleeSideArgEffects = FunctionSummary->getArgEffects();
+
+ for (unsigned idx = 0, e = FD->getNumParams(); idx != e; ++idx) {
+ const ParmVarDecl *Param = FD->getParamDecl(idx);
+ SymbolRef Sym = state->getSVal(state->getRegion(Param, LCtx)).getAsSymbol();
+
+ QualType Ty = Param->getType();
+ const ArgEffect *AE = CalleeSideArgEffects.lookup(idx);
+ if (AE && AE->getKind() == DecRef && isISLObjectRef(Ty)) {
+ state = setRefBinding(
+ state, Sym, RefVal::makeOwned(ObjKind::Generalized, Ty));
+ } else if (isISLObjectRef(Ty)) {
+ state = setRefBinding(
+ state, Sym,
+ RefVal::makeNotOwned(ObjKind::Generalized, Ty));
+ }
+ }
+
+ Ctx.addTransition(state);
+}
+
+void RetainCountChecker::checkEndFunction(const ReturnStmt *RS,
+ CheckerContext &Ctx) const {
+ ExplodedNode *Pred = processReturn(RS, Ctx);
+
+ // Created state cached out.
+ if (!Pred) {
+ return;
+ }
+
+ ProgramStateRef state = Pred->getState();
+ RefBindingsTy B = state->get<RefBindings>();
+
+ // Don't process anything within synthesized bodies.
+ const LocationContext *LCtx = Pred->getLocationContext();
+ if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) {
+ assert(!LCtx->inTopFrame());
+ return;
+ }
+
+ for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
+ state = handleAutoreleaseCounts(state, Pred, /*Tag=*/nullptr, Ctx,
+ I->first, I->second);
+ if (!state)
+ return;
+ }
+
+ // If the current LocationContext has a parent, don't check for leaks.
+ // We will do that later.
+ // FIXME: we should instead check for imbalances of the retain/releases,
+ // and suggest annotations.
+ if (LCtx->getParent())
+ return;
+
+ B = state->get<RefBindings>();
+ SmallVector<SymbolRef, 10> Leaked;
+
+ for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I)
+ state = handleSymbolDeath(state, I->first, I->second, Leaked);
+
+ processLeaks(state, Leaked, Ctx, Pred);
+}
+
+void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper,
+ CheckerContext &C) const {
+ ExplodedNode *Pred = C.getPredecessor();
+
+ ProgramStateRef state = C.getState();
+ RefBindingsTy B = state->get<RefBindings>();
+ SmallVector<SymbolRef, 10> Leaked;
+
+ // Update counts from autorelease pools
+ for (const auto &I: state->get<RefBindings>()) {
+ SymbolRef Sym = I.first;
+ if (SymReaper.isDead(Sym)) {
+ static CheckerProgramPointTag Tag(this, "DeadSymbolAutorelease");
+ const RefVal &V = I.second;
+ state = handleAutoreleaseCounts(state, Pred, &Tag, C, Sym, V);
+ if (!state)
+ return;
+
+ // Fetch the new reference count from the state, and use it to handle
+ // this symbol.
+ state = handleSymbolDeath(state, Sym, *getRefBinding(state, Sym), Leaked);
+ }
+ }
+
+ if (Leaked.empty()) {
+ C.addTransition(state);
+ return;
+ }
+
+ Pred = processLeaks(state, Leaked, C, Pred);
+
+ // Did we cache out?
+ if (!Pred)
+ return;
+
+ // Now generate a new node that nukes the old bindings.
+ // The only bindings left at this point are the leaked symbols.
+ RefBindingsTy::Factory &F = state->get_context<RefBindings>();
+ B = state->get<RefBindings>();
+
+ for (SmallVectorImpl<SymbolRef>::iterator I = Leaked.begin(),
+ E = Leaked.end();
+ I != E; ++I)
+ B = F.remove(B, *I);
+
+ state = state->set<RefBindings>(B);
+ C.addTransition(state, Pred);
+}
+
+void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const {
+
+ RefBindingsTy B = State->get<RefBindings>();
+
+ if (B.isEmpty())
+ return;
+
+ Out << Sep << NL;
+
+ for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) {
+ Out << I->first << " : ";
+ I->second.print(Out);
+ Out << NL;
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Checker registration.
+//===----------------------------------------------------------------------===//
+
+void ento::registerRetainCountChecker(CheckerManager &Mgr) {
+ auto *Chk = Mgr.registerChecker<RetainCountChecker>();
+ Chk->TrackObjCAndCFObjects = true;
+}
+
+// FIXME: remove this, hack for backwards compatibility:
+// it should be possible to enable the NS/CF retain count checker as
+// osx.cocoa.RetainCount, and it should be possible to disable
+// osx.OSObjectRetainCount using osx.cocoa.RetainCount:CheckOSObject=false.
+static bool hasPrevCheckOSObjectOptionDisabled(AnalyzerOptions &Options) {
+ auto I = Options.Config.find("osx.cocoa.RetainCount:CheckOSObject");
+ if (I != Options.Config.end())
+ return I->getValue() == "false";
+ return false;
+}
+
+void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) {
+ auto *Chk = Mgr.registerChecker<RetainCountChecker>();
+ if (!hasPrevCheckOSObjectOptionDisabled(Mgr.getAnalyzerOptions()))
+ Chk->TrackOSObjects = true;
+}
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h
new file mode 100644
index 000000000000..31e2d9ae4932
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h
@@ -0,0 +1,393 @@
+//==--- RetainCountChecker.h - Checks for leaks and other issues -*- 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 methods for RetainCountChecker, which implements
+// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "RetainCountDiagnostics.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
+#include "clang/Basic/LangOptions.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Analysis/SelectorExtras.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
+#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/FoldingSet.h"
+#include "llvm/ADT/ImmutableList.h"
+#include "llvm/ADT/ImmutableMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
+#include <cstdarg>
+#include <utility>
+
+namespace clang {
+namespace ento {
+namespace retaincountchecker {
+
+/// Metadata on reference.
+class RefVal {
+public:
+ enum Kind {
+ Owned = 0, // Owning reference.
+ NotOwned, // Reference is not owned by still valid (not freed).
+ Released, // Object has been released.
+ ReturnedOwned, // Returned object passes ownership to caller.
+ ReturnedNotOwned, // Return object does not pass ownership to caller.
+ ERROR_START,
+ ErrorDeallocNotOwned, // -dealloc called on non-owned object.
+ ErrorUseAfterRelease, // Object used after released.
+ ErrorReleaseNotOwned, // Release of an object that was not owned.
+ ERROR_LEAK_START,
+ ErrorLeak, // A memory leak due to excessive reference counts.
+ ErrorLeakReturned, // A memory leak due to the returning method not having
+ // the correct naming conventions.
+ ErrorOverAutorelease,
+ ErrorReturnedNotOwned
+ };
+
+ /// Tracks how an object referenced by an ivar has been used.
+ ///
+ /// This accounts for us not knowing if an arbitrary ivar is supposed to be
+ /// stored at +0 or +1.
+ enum class IvarAccessHistory {
+ None,
+ AccessedDirectly,
+ ReleasedAfterDirectAccess
+ };
+
+private:
+ /// The number of outstanding retains.
+ unsigned Cnt;
+ /// The number of outstanding autoreleases.
+ unsigned ACnt;
+ /// The (static) type of the object at the time we started tracking it.
+ QualType T;
+
+ /// The current state of the object.
+ ///
+ /// See the RefVal::Kind enum for possible values.
+ unsigned RawKind : 5;
+
+ /// The kind of object being tracked (CF or ObjC or OSObject), if known.
+ ///
+ /// See the ObjKind enum for possible values.
+ unsigned RawObjectKind : 3;
+
+ /// True if the current state and/or retain count may turn out to not be the
+ /// best possible approximation of the reference counting state.
+ ///
+ /// If true, the checker may decide to throw away ("override") this state
+ /// in favor of something else when it sees the object being used in new ways.
+ ///
+ /// This setting should not be propagated to state derived from this state.
+ /// Once we start deriving new states, it would be inconsistent to override
+ /// them.
+ unsigned RawIvarAccessHistory : 2;
+
+ RefVal(Kind k, ObjKind o, unsigned cnt, unsigned acnt, QualType t,
+ IvarAccessHistory IvarAccess)
+ : Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)),
+ RawObjectKind(static_cast<unsigned>(o)),
+ RawIvarAccessHistory(static_cast<unsigned>(IvarAccess)) {
+ assert(getKind() == k && "not enough bits for the kind");
+ assert(getObjKind() == o && "not enough bits for the object kind");
+ assert(getIvarAccessHistory() == IvarAccess && "not enough bits");
+ }
+
+public:
+ Kind getKind() const { return static_cast<Kind>(RawKind); }
+
+ ObjKind getObjKind() const {
+ return static_cast<ObjKind>(RawObjectKind);
+ }
+
+ unsigned getCount() const { return Cnt; }
+ unsigned getAutoreleaseCount() const { return ACnt; }
+ unsigned getCombinedCounts() const { return Cnt + ACnt; }
+ void clearCounts() {
+ Cnt = 0;
+ ACnt = 0;
+ }
+ void setCount(unsigned i) {
+ Cnt = i;
+ }
+ void setAutoreleaseCount(unsigned i) {
+ ACnt = i;
+ }
+
+ QualType getType() const { return T; }
+
+ /// Returns what the analyzer knows about direct accesses to a particular
+ /// instance variable.
+ ///
+ /// If the object with this refcount wasn't originally from an Objective-C
+ /// ivar region, this should always return IvarAccessHistory::None.
+ IvarAccessHistory getIvarAccessHistory() const {
+ return static_cast<IvarAccessHistory>(RawIvarAccessHistory);
+ }
+
+ bool isOwned() const {
+ return getKind() == Owned;
+ }
+
+ bool isNotOwned() const {
+ return getKind() == NotOwned;
+ }
+
+ bool isReturnedOwned() const {
+ return getKind() == ReturnedOwned;
+ }
+
+ bool isReturnedNotOwned() const {
+ return getKind() == ReturnedNotOwned;
+ }
+
+ /// Create a state for an object whose lifetime is the responsibility of the
+ /// current function, at least partially.
+ ///
+ /// Most commonly, this is an owned object with a retain count of +1.
+ static RefVal makeOwned(ObjKind o, QualType t) {
+ return RefVal(Owned, o, /*Count=*/1, 0, t, IvarAccessHistory::None);
+ }
+
+ /// Create a state for an object whose lifetime is not the responsibility of
+ /// the current function.
+ ///
+ /// Most commonly, this is an unowned object with a retain count of +0.
+ static RefVal makeNotOwned(ObjKind o, QualType t) {
+ return RefVal(NotOwned, o, /*Count=*/0, 0, t, IvarAccessHistory::None);
+ }
+
+ RefVal operator-(size_t i) const {
+ return RefVal(getKind(), getObjKind(), getCount() - i,
+ getAutoreleaseCount(), getType(), getIvarAccessHistory());
+ }
+
+ RefVal operator+(size_t i) const {
+ return RefVal(getKind(), getObjKind(), getCount() + i,
+ getAutoreleaseCount(), getType(), getIvarAccessHistory());
+ }
+
+ RefVal operator^(Kind k) const {
+ return RefVal(k, getObjKind(), getCount(), getAutoreleaseCount(),
+ getType(), getIvarAccessHistory());
+ }
+
+ RefVal autorelease() const {
+ return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount()+1,
+ getType(), getIvarAccessHistory());
+ }
+
+ RefVal withIvarAccess() const {
+ assert(getIvarAccessHistory() == IvarAccessHistory::None);
+ return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
+ getType(), IvarAccessHistory::AccessedDirectly);
+ }
+
+ RefVal releaseViaIvar() const {
+ assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly);
+ return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(),
+ getType(), IvarAccessHistory::ReleasedAfterDirectAccess);
+ }
+
+ // Comparison, profiling, and pretty-printing.
+ bool hasSameState(const RefVal &X) const {
+ return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt &&
+ getIvarAccessHistory() == X.getIvarAccessHistory();
+ }
+
+ bool operator==(const RefVal& X) const {
+ return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind();
+ }
+
+ void Profile(llvm::FoldingSetNodeID& ID) const {
+ ID.Add(T);
+ ID.AddInteger(RawKind);
+ ID.AddInteger(Cnt);
+ ID.AddInteger(ACnt);
+ ID.AddInteger(RawObjectKind);
+ ID.AddInteger(RawIvarAccessHistory);
+ }
+
+ void print(raw_ostream &Out) const;
+};
+
+class RetainCountChecker
+ : public Checker< check::Bind,
+ check::DeadSymbols,
+ check::BeginFunction,
+ check::EndFunction,
+ check::PostStmt<BlockExpr>,
+ check::PostStmt<CastExpr>,
+ check::PostStmt<ObjCArrayLiteral>,
+ check::PostStmt<ObjCDictionaryLiteral>,
+ check::PostStmt<ObjCBoxedExpr>,
+ check::PostStmt<ObjCIvarRefExpr>,
+ check::PostCall,
+ check::RegionChanges,
+ eval::Assume,
+ eval::Call > {
+ mutable std::unique_ptr<RefCountBug> useAfterRelease, releaseNotOwned;
+ mutable std::unique_ptr<RefCountBug> deallocNotOwned;
+ mutable std::unique_ptr<RefCountBug> overAutorelease, returnNotOwnedForOwned;
+ mutable std::unique_ptr<RefCountBug> leakWithinFunction, leakAtReturn;
+
+ mutable std::unique_ptr<RetainSummaryManager> Summaries;
+public:
+ static constexpr const char *DeallocTagDescription = "DeallocSent";
+
+ /// Track Objective-C and CoreFoundation objects.
+ bool TrackObjCAndCFObjects = false;
+
+ /// Track sublcasses of OSObject.
+ bool TrackOSObjects = false;
+
+ RetainCountChecker() {}
+
+ RefCountBug *getLeakWithinFunctionBug(const LangOptions &LOpts) const;
+
+ RefCountBug *getLeakAtReturnBug(const LangOptions &LOpts) const;
+
+ RetainSummaryManager &getSummaryManager(ASTContext &Ctx) const {
+ // FIXME: We don't support ARC being turned on and off during one analysis.
+ // (nor, for that matter, do we support changing ASTContexts)
+ bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount;
+ if (!Summaries) {
+ Summaries.reset(new RetainSummaryManager(
+ Ctx, ARCEnabled, TrackObjCAndCFObjects, TrackOSObjects));
+ } else {
+ assert(Summaries->isARCEnabled() == ARCEnabled);
+ }
+ return *Summaries;
+ }
+
+ RetainSummaryManager &getSummaryManager(CheckerContext &C) const {
+ return getSummaryManager(C.getASTContext());
+ }
+
+ void printState(raw_ostream &Out, ProgramStateRef State,
+ const char *NL, const char *Sep) const override;
+
+ void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const;
+ void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const;
+ void checkPostStmt(const CastExpr *CE, CheckerContext &C) const;
+
+ void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const;
+ void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const;
+ void checkPostStmt(const ObjCBoxedExpr *BE, CheckerContext &C) const;
+
+ void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const;
+
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
+
+ void checkSummary(const RetainSummary &Summ, const CallEvent &Call,
+ CheckerContext &C) const;
+
+ void processSummaryOfInlined(const RetainSummary &Summ,
+ const CallEvent &Call,
+ CheckerContext &C) const;
+
+ bool evalCall(const CallExpr *CE, CheckerContext &C) const;
+
+ ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond,
+ bool Assumption) const;
+
+ ProgramStateRef
+ checkRegionChanges(ProgramStateRef state,
+ const InvalidatedSymbols *invalidated,
+ ArrayRef<const MemRegion *> ExplicitRegions,
+ ArrayRef<const MemRegion *> Regions,
+ const LocationContext* LCtx,
+ const CallEvent *Call) const;
+
+ ExplodedNode* checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C,
+ ExplodedNode *Pred, RetEffect RE, RefVal X,
+ SymbolRef Sym, ProgramStateRef state) const;
+
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
+ void checkBeginFunction(CheckerContext &C) const;
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+
+ ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym,
+ RefVal V, ArgEffect E, RefVal::Kind &hasErr,
+ CheckerContext &C) const;
+
+ void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange,
+ RefVal::Kind ErrorKind, SymbolRef Sym,
+ CheckerContext &C) const;
+
+ void processObjCLiterals(CheckerContext &C, const Expr *Ex) const;
+
+ ProgramStateRef handleSymbolDeath(ProgramStateRef state,
+ SymbolRef sid, RefVal V,
+ SmallVectorImpl<SymbolRef> &Leaked) const;
+
+ ProgramStateRef
+ handleAutoreleaseCounts(ProgramStateRef state, ExplodedNode *Pred,
+ const ProgramPointTag *Tag, CheckerContext &Ctx,
+ SymbolRef Sym,
+ RefVal V,
+ const ReturnStmt *S=nullptr) const;
+
+ ExplodedNode *processLeaks(ProgramStateRef state,
+ SmallVectorImpl<SymbolRef> &Leaked,
+ CheckerContext &Ctx,
+ ExplodedNode *Pred = nullptr) const;
+
+private:
+ /// Perform the necessary checks and state adjustments at the end of the
+ /// function.
+ /// \p S Return statement, may be null.
+ ExplodedNode * processReturn(const ReturnStmt *S, CheckerContext &C) const;
+};
+
+//===----------------------------------------------------------------------===//
+// RefBindings - State used to track object reference counts.
+//===----------------------------------------------------------------------===//
+
+const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym);
+
+ProgramStateRef setRefBinding(ProgramStateRef State, SymbolRef Sym,
+ RefVal Val);
+
+ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym);
+
+/// Returns true if this stack frame is for an Objective-C method that is a
+/// property getter or setter whose body has been synthesized by the analyzer.
+inline bool isSynthesizedAccessor(const StackFrameContext *SFC) {
+ auto Method = dyn_cast_or_null<ObjCMethodDecl>(SFC->getDecl());
+ if (!Method || !Method->isPropertyAccessor())
+ return false;
+
+ return SFC->getAnalysisDeclContext()->isBodyAutosynthesized();
+}
+
+} // end namespace retaincountchecker
+} // end namespace ento
+} // end namespace clang
+
+#endif
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp
new file mode 100644
index 000000000000..cda1a928de13
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp
@@ -0,0 +1,794 @@
+// RetainCountDiagnostics.cpp - Checks for leaks and other issues -*- 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 diagnostics for RetainCountChecker, which implements
+// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
+//
+//===----------------------------------------------------------------------===//
+
+#include "RetainCountDiagnostics.h"
+#include "RetainCountChecker.h"
+
+using namespace clang;
+using namespace ento;
+using namespace retaincountchecker;
+
+static bool isNumericLiteralExpression(const Expr *E) {
+ // FIXME: This set of cases was copied from SemaExprObjC.
+ return isa<IntegerLiteral>(E) ||
+ isa<CharacterLiteral>(E) ||
+ isa<FloatingLiteral>(E) ||
+ isa<ObjCBoolLiteralExpr>(E) ||
+ isa<CXXBoolLiteralExpr>(E);
+}
+
+/// If type represents a pointer to CXXRecordDecl,
+/// and is not a typedef, return the decl name.
+/// Otherwise, return the serialization of type.
+static std::string getPrettyTypeName(QualType QT) {
+ QualType PT = QT->getPointeeType();
+ if (!PT.isNull() && !QT->getAs<TypedefType>())
+ if (const auto *RD = PT->getAsCXXRecordDecl())
+ return RD->getName();
+ return QT.getAsString();
+}
+
+/// Write information about the type state change to {@code os},
+/// return whether the note should be generated.
+static bool shouldGenerateNote(llvm::raw_string_ostream &os,
+ const RefVal *PrevT, const RefVal &CurrV,
+ bool DeallocSent) {
+ // Get the previous type state.
+ RefVal PrevV = *PrevT;
+
+ // Specially handle -dealloc.
+ if (DeallocSent) {
+ // Determine if the object's reference count was pushed to zero.
+ assert(!PrevV.hasSameState(CurrV) && "The state should have changed.");
+ // We may not have transitioned to 'release' if we hit an error.
+ // This case is handled elsewhere.
+ if (CurrV.getKind() == RefVal::Released) {
+ assert(CurrV.getCombinedCounts() == 0);
+ os << "Object released by directly sending the '-dealloc' message";
+ return true;
+ }
+ }
+
+ // Determine if the typestate has changed.
+ if (!PrevV.hasSameState(CurrV))
+ switch (CurrV.getKind()) {
+ case RefVal::Owned:
+ case RefVal::NotOwned:
+ if (PrevV.getCount() == CurrV.getCount()) {
+ // Did an autorelease message get sent?
+ if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount())
+ return false;
+
+ assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount());
+ os << "Object autoreleased";
+ return true;
+ }
+
+ if (PrevV.getCount() > CurrV.getCount())
+ os << "Reference count decremented.";
+ else
+ os << "Reference count incremented.";
+
+ if (unsigned Count = CurrV.getCount())
+ os << " The object now has a +" << Count << " retain count.";
+
+ return true;
+
+ case RefVal::Released:
+ if (CurrV.getIvarAccessHistory() ==
+ RefVal::IvarAccessHistory::ReleasedAfterDirectAccess &&
+ CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) {
+ os << "Strong instance variable relinquished. ";
+ }
+ os << "Object released.";
+ return true;
+
+ case RefVal::ReturnedOwned:
+ // Autoreleases can be applied after marking a node ReturnedOwned.
+ if (CurrV.getAutoreleaseCount())
+ return false;
+
+ os << "Object returned to caller as an owning reference (single "
+ "retain count transferred to caller)";
+ return true;
+
+ case RefVal::ReturnedNotOwned:
+ os << "Object returned to caller with a +0 retain count";
+ return true;
+
+ default:
+ return false;
+ }
+ return true;
+}
+
+/// Finds argument index of the out paramter in the call {@code S}
+/// corresponding to the symbol {@code Sym}.
+/// If none found, returns None.
+static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt,
+ const LocationContext *LCtx,
+ SymbolRef &Sym,
+ Optional<CallEventRef<>> CE) {
+ if (!CE)
+ return None;
+
+ for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++)
+ if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion())
+ if (const auto *TR = dyn_cast<TypedValueRegion>(MR))
+ if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymExpr() == Sym)
+ return Idx;
+
+ return None;
+}
+
+static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt,
+ const LocationContext *LCtx,
+ const RefVal &CurrV, SymbolRef &Sym,
+ const Stmt *S,
+ llvm::raw_string_ostream &os) {
+ CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager();
+ if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
+ // Get the name of the callee (if it is available)
+ // from the tracked SVal.
+ SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx);
+ const FunctionDecl *FD = X.getAsFunctionDecl();
+
+ // If failed, try to get it from AST.
+ if (!FD)
+ FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl());
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(CE->getCalleeDecl())) {
+ os << "Call to method '" << MD->getQualifiedNameAsString() << '\'';
+ } else if (FD) {
+ os << "Call to function '" << FD->getQualifiedNameAsString() << '\'';
+ } else {
+ os << "function call";
+ }
+ } else if (isa<CXXNewExpr>(S)) {
+ os << "Operator 'new'";
+ } else {
+ assert(isa<ObjCMessageExpr>(S));
+ CallEventRef<ObjCMethodCall> Call =
+ Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx);
+
+ switch (Call->getMessageKind()) {
+ case OCM_Message:
+ os << "Method";
+ break;
+ case OCM_PropertyAccess:
+ os << "Property";
+ break;
+ case OCM_Subscript:
+ os << "Subscript";
+ break;
+ }
+ }
+
+ Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx);
+ auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE);
+
+ // If index is not found, we assume that the symbol was returned.
+ if (!Idx) {
+ os << " returns ";
+ } else {
+ os << " writes ";
+ }
+
+ if (CurrV.getObjKind() == ObjKind::CF) {
+ os << "a Core Foundation object of type '"
+ << Sym->getType().getAsString() << "' with a ";
+ } else if (CurrV.getObjKind() == ObjKind::OS) {
+ os << "an OSObject of type '" << getPrettyTypeName(Sym->getType())
+ << "' with a ";
+ } else if (CurrV.getObjKind() == ObjKind::Generalized) {
+ os << "an object of type '" << Sym->getType().getAsString()
+ << "' with a ";
+ } else {
+ assert(CurrV.getObjKind() == ObjKind::ObjC);
+ QualType T = Sym->getType();
+ if (!isa<ObjCObjectPointerType>(T)) {
+ os << "an Objective-C object with a ";
+ } else {
+ const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T);
+ os << "an instance of " << PT->getPointeeType().getAsString()
+ << " with a ";
+ }
+ }
+
+ if (CurrV.isOwned()) {
+ os << "+1 retain count";
+ } else {
+ assert(CurrV.isNotOwned());
+ os << "+0 retain count";
+ }
+
+ if (Idx) {
+ os << " into an out parameter '";
+ const ParmVarDecl *PVD = (*CE)->parameters()[*Idx];
+ PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),
+ /*Qualified=*/false);
+ os << "'";
+
+ QualType RT = (*CE)->getResultType();
+ if (!RT.isNull() && !RT->isVoidType()) {
+ SVal RV = (*CE)->getReturnValue();
+ if (CurrSt->isNull(RV).isConstrainedTrue()) {
+ os << " (assuming the call returns zero)";
+ } else if (CurrSt->isNonNull(RV).isConstrainedTrue()) {
+ os << " (assuming the call returns non-zero)";
+ }
+
+ }
+ }
+}
+
+namespace clang {
+namespace ento {
+namespace retaincountchecker {
+
+class RefCountReportVisitor : public BugReporterVisitor {
+protected:
+ SymbolRef Sym;
+
+public:
+ RefCountReportVisitor(SymbolRef sym) : Sym(sym) {}
+
+ void Profile(llvm::FoldingSetNodeID &ID) const override {
+ static int x = 0;
+ ID.AddPointer(&x);
+ ID.AddPointer(Sym);
+ }
+
+ std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC,
+ BugReport &BR) override;
+
+ std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *N,
+ BugReport &BR) override;
+};
+
+class RefLeakReportVisitor : public RefCountReportVisitor {
+public:
+ RefLeakReportVisitor(SymbolRef sym) : RefCountReportVisitor(sym) {}
+
+ std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *N,
+ BugReport &BR) override;
+};
+
+} // end namespace retaincountchecker
+} // end namespace ento
+} // end namespace clang
+
+
+/// Find the first node with the parent stack frame.
+static const ExplodedNode *getCalleeNode(const ExplodedNode *Pred) {
+ const StackFrameContext *SC = Pred->getStackFrame();
+ if (SC->inTopFrame())
+ return nullptr;
+ const StackFrameContext *PC = SC->getParent()->getStackFrame();
+ if (!PC)
+ return nullptr;
+
+ const ExplodedNode *N = Pred;
+ while (N && N->getStackFrame() != PC) {
+ N = N->getFirstPred();
+ }
+ return N;
+}
+
+
+/// Insert a diagnostic piece at function exit
+/// if a function parameter is annotated as "os_consumed",
+/// but it does not actually consume the reference.
+static std::shared_ptr<PathDiagnosticEventPiece>
+annotateConsumedSummaryMismatch(const ExplodedNode *N,
+ CallExitBegin &CallExitLoc,
+ const SourceManager &SM,
+ CallEventManager &CEMgr) {
+
+ const ExplodedNode *CN = getCalleeNode(N);
+ if (!CN)
+ return nullptr;
+
+ CallEventRef<> Call = CEMgr.getCaller(N->getStackFrame(), N->getState());
+
+ std::string sbuf;
+ llvm::raw_string_ostream os(sbuf);
+ ArrayRef<const ParmVarDecl *> Parameters = Call->parameters();
+ for (unsigned I=0; I < Call->getNumArgs() && I < Parameters.size(); ++I) {
+ const ParmVarDecl *PVD = Parameters[I];
+
+ if (!PVD->hasAttr<OSConsumedAttr>())
+ continue;
+
+ if (SymbolRef SR = Call->getArgSVal(I).getAsLocSymbol()) {
+ const RefVal *CountBeforeCall = getRefBinding(CN->getState(), SR);
+ const RefVal *CountAtExit = getRefBinding(N->getState(), SR);
+
+ if (!CountBeforeCall || !CountAtExit)
+ continue;
+
+ unsigned CountBefore = CountBeforeCall->getCount();
+ unsigned CountAfter = CountAtExit->getCount();
+
+ bool AsExpected = CountBefore > 0 && CountAfter == CountBefore - 1;
+ if (!AsExpected) {
+ os << "Parameter '";
+ PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(),
+ /*Qualified=*/false);
+ os << "' is marked as consuming, but the function did not consume "
+ << "the reference\n";
+ }
+ }
+ }
+
+ if (os.str().empty())
+ return nullptr;
+
+ // FIXME: remove the code duplication with NoStoreFuncVisitor.
+ PathDiagnosticLocation L;
+ if (const ReturnStmt *RS = CallExitLoc.getReturnStmt()) {
+ L = PathDiagnosticLocation::createBegin(RS, SM, N->getLocationContext());
+ } else {
+ L = PathDiagnosticLocation(
+ Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM);
+ }
+
+ return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
+}
+
+std::shared_ptr<PathDiagnosticPiece>
+RefCountReportVisitor::VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC, BugReport &BR) {
+
+ const SourceManager &SM = BRC.getSourceManager();
+ CallEventManager &CEMgr = BRC.getStateManager().getCallEventManager();
+ if (auto CE = N->getLocationAs<CallExitBegin>())
+ if (auto PD = annotateConsumedSummaryMismatch(N, *CE, SM, CEMgr))
+ return PD;
+
+ // FIXME: We will eventually need to handle non-statement-based events
+ // (__attribute__((cleanup))).
+ if (!N->getLocation().getAs<StmtPoint>())
+ return nullptr;
+
+ // Check if the type state has changed.
+ const ExplodedNode *PrevNode = N->getFirstPred();
+ ProgramStateRef PrevSt = PrevNode->getState();
+ ProgramStateRef CurrSt = N->getState();
+ const LocationContext *LCtx = N->getLocationContext();
+
+ const RefVal* CurrT = getRefBinding(CurrSt, Sym);
+ if (!CurrT) return nullptr;
+
+ const RefVal &CurrV = *CurrT;
+ const RefVal *PrevT = getRefBinding(PrevSt, Sym);
+
+ // Create a string buffer to constain all the useful things we want
+ // to tell the user.
+ std::string sbuf;
+ llvm::raw_string_ostream os(sbuf);
+
+ // This is the allocation site since the previous node had no bindings
+ // for this symbol.
+ if (!PrevT) {
+ const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
+
+ if (isa<ObjCIvarRefExpr>(S) &&
+ isSynthesizedAccessor(LCtx->getStackFrame())) {
+ S = LCtx->getStackFrame()->getCallSite();
+ }
+
+ if (isa<ObjCArrayLiteral>(S)) {
+ os << "NSArray literal is an object with a +0 retain count";
+ } else if (isa<ObjCDictionaryLiteral>(S)) {
+ os << "NSDictionary literal is an object with a +0 retain count";
+ } else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) {
+ if (isNumericLiteralExpression(BL->getSubExpr()))
+ os << "NSNumber literal is an object with a +0 retain count";
+ else {
+ const ObjCInterfaceDecl *BoxClass = nullptr;
+ if (const ObjCMethodDecl *Method = BL->getBoxingMethod())
+ BoxClass = Method->getClassInterface();
+
+ // We should always be able to find the boxing class interface,
+ // but consider this future-proofing.
+ if (BoxClass) {
+ os << *BoxClass << " b";
+ } else {
+ os << "B";
+ }
+
+ os << "oxed expression produces an object with a +0 retain count";
+ }
+ } else if (isa<ObjCIvarRefExpr>(S)) {
+ os << "Object loaded from instance variable";
+ } else {
+ generateDiagnosticsForCallLike(CurrSt, LCtx, CurrV, Sym, S, os);
+ }
+
+ PathDiagnosticLocation Pos(S, SM, N->getLocationContext());
+ return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
+ }
+
+ // Gather up the effects that were performed on the object at this
+ // program point
+ bool DeallocSent = false;
+
+ if (N->getLocation().getTag() &&
+ N->getLocation().getTag()->getTagDescription().contains(
+ RetainCountChecker::DeallocTagDescription)) {
+ // We only have summaries attached to nodes after evaluating CallExpr and
+ // ObjCMessageExprs.
+ const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
+
+ if (const CallExpr *CE = dyn_cast<CallExpr>(S)) {
+ // Iterate through the parameter expressions and see if the symbol
+ // was ever passed as an argument.
+ unsigned i = 0;
+
+ for (auto AI=CE->arg_begin(), AE=CE->arg_end(); AI!=AE; ++AI, ++i) {
+
+ // Retrieve the value of the argument. Is it the symbol
+ // we are interested in?
+ if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym)
+ continue;
+
+ // We have an argument. Get the effect!
+ DeallocSent = true;
+ }
+ } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) {
+ if (const Expr *receiver = ME->getInstanceReceiver()) {
+ if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx)
+ .getAsLocSymbol() == Sym) {
+ // The symbol we are tracking is the receiver.
+ DeallocSent = true;
+ }
+ }
+ }
+ }
+
+ if (!shouldGenerateNote(os, PrevT, CurrV, DeallocSent))
+ return nullptr;
+
+ if (os.str().empty())
+ return nullptr; // We have nothing to say!
+
+ const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt();
+ PathDiagnosticLocation Pos(S, BRC.getSourceManager(),
+ N->getLocationContext());
+ auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str());
+
+ // Add the range by scanning the children of the statement for any bindings
+ // to Sym.
+ for (const Stmt *Child : S->children())
+ if (const Expr *Exp = dyn_cast_or_null<Expr>(Child))
+ if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) {
+ P->addRange(Exp->getSourceRange());
+ break;
+ }
+
+ return std::move(P);
+}
+
+static Optional<std::string> describeRegion(const MemRegion *MR) {
+ if (const auto *VR = dyn_cast_or_null<VarRegion>(MR))
+ return std::string(VR->getDecl()->getName());
+ // Once we support more storage locations for bindings,
+ // this would need to be improved.
+ return None;
+}
+
+namespace {
+// Find the first node in the current function context that referred to the
+// tracked symbol and the memory location that value was stored to. Note, the
+// value is only reported if the allocation occurred in the same function as
+// the leak. The function can also return a location context, which should be
+// treated as interesting.
+struct AllocationInfo {
+ const ExplodedNode* N;
+ const MemRegion *R;
+ const LocationContext *InterestingMethodContext;
+ AllocationInfo(const ExplodedNode *InN,
+ const MemRegion *InR,
+ const LocationContext *InInterestingMethodContext) :
+ N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {}
+};
+} // end anonymous namespace
+
+static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr,
+ const ExplodedNode *N, SymbolRef Sym) {
+ const ExplodedNode *AllocationNode = N;
+ const ExplodedNode *AllocationNodeInCurrentOrParentContext = N;
+ const MemRegion *FirstBinding = nullptr;
+ const LocationContext *LeakContext = N->getLocationContext();
+
+ // The location context of the init method called on the leaked object, if
+ // available.
+ const LocationContext *InitMethodContext = nullptr;
+
+ while (N) {
+ ProgramStateRef St = N->getState();
+ const LocationContext *NContext = N->getLocationContext();
+
+ if (!getRefBinding(St, Sym))
+ break;
+
+ StoreManager::FindUniqueBinding FB(Sym);
+ StateMgr.iterBindings(St, FB);
+
+ if (FB) {
+ const MemRegion *R = FB.getRegion();
+ // Do not show local variables belonging to a function other than
+ // where the error is reported.
+ if (auto MR = dyn_cast<StackSpaceRegion>(R->getMemorySpace()))
+ if (MR->getStackFrame() == LeakContext->getStackFrame())
+ FirstBinding = R;
+ }
+
+ // AllocationNode is the last node in which the symbol was tracked.
+ AllocationNode = N;
+
+ // AllocationNodeInCurrentContext, is the last node in the current or
+ // parent context in which the symbol was tracked.
+ //
+ // Note that the allocation site might be in the parent context. For example,
+ // the case where an allocation happens in a block that captures a reference
+ // to it and that reference is overwritten/dropped by another call to
+ // the block.
+ if (NContext == LeakContext || NContext->isParentOf(LeakContext))
+ AllocationNodeInCurrentOrParentContext = N;
+
+ // Find the last init that was called on the given symbol and store the
+ // init method's location context.
+ if (!InitMethodContext)
+ if (auto CEP = N->getLocation().getAs<CallEnter>()) {
+ const Stmt *CE = CEP->getCallExpr();
+ if (const auto *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) {
+ const Stmt *RecExpr = ME->getInstanceReceiver();
+ if (RecExpr) {
+ SVal RecV = St->getSVal(RecExpr, NContext);
+ if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym)
+ InitMethodContext = CEP->getCalleeContext();
+ }
+ }
+ }
+
+ N = N->getFirstPred();
+ }
+
+ // If we are reporting a leak of the object that was allocated with alloc,
+ // mark its init method as interesting.
+ const LocationContext *InterestingMethodContext = nullptr;
+ if (InitMethodContext) {
+ const ProgramPoint AllocPP = AllocationNode->getLocation();
+ if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>())
+ if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>())
+ if (ME->getMethodFamily() == OMF_alloc)
+ InterestingMethodContext = InitMethodContext;
+ }
+
+ // If allocation happened in a function different from the leak node context,
+ // do not report the binding.
+ assert(N && "Could not find allocation node");
+
+ if (AllocationNodeInCurrentOrParentContext &&
+ AllocationNodeInCurrentOrParentContext->getLocationContext() !=
+ LeakContext)
+ FirstBinding = nullptr;
+
+ return AllocationInfo(AllocationNodeInCurrentOrParentContext,
+ FirstBinding,
+ InterestingMethodContext);
+}
+
+std::shared_ptr<PathDiagnosticPiece>
+RefCountReportVisitor::getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *EndN, BugReport &BR) {
+ BR.markInteresting(Sym);
+ return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR);
+}
+
+std::shared_ptr<PathDiagnosticPiece>
+RefLeakReportVisitor::getEndPath(BugReporterContext &BRC,
+ const ExplodedNode *EndN, BugReport &BR) {
+
+ // Tell the BugReporterContext to report cases when the tracked symbol is
+ // assigned to different variables, etc.
+ BR.markInteresting(Sym);
+
+ // We are reporting a leak. Walk up the graph to get to the first node where
+ // the symbol appeared, and also get the first VarDecl that tracked object
+ // is stored to.
+ AllocationInfo AllocI = GetAllocationSite(BRC.getStateManager(), EndN, Sym);
+
+ const MemRegion* FirstBinding = AllocI.R;
+ BR.markInteresting(AllocI.InterestingMethodContext);
+
+ SourceManager& SM = BRC.getSourceManager();
+
+ // Compute an actual location for the leak. Sometimes a leak doesn't
+ // occur at an actual statement (e.g., transition between blocks; end
+ // of function) so we need to walk the graph and compute a real location.
+ const ExplodedNode *LeakN = EndN;
+ PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM);
+
+ std::string sbuf;
+ llvm::raw_string_ostream os(sbuf);
+
+ os << "Object leaked: ";
+
+ Optional<std::string> RegionDescription = describeRegion(FirstBinding);
+ if (RegionDescription) {
+ os << "object allocated and stored into '" << *RegionDescription << '\'';
+ } else {
+ os << "allocated object of type '" << getPrettyTypeName(Sym->getType())
+ << "'";
+ }
+
+ // Get the retain count.
+ const RefVal* RV = getRefBinding(EndN->getState(), Sym);
+ assert(RV);
+
+ if (RV->getKind() == RefVal::ErrorLeakReturned) {
+ // FIXME: Per comments in rdar://6320065, "create" only applies to CF
+ // objects. Only "copy", "alloc", "retain" and "new" transfer ownership
+ // to the caller for NS objects.
+ const Decl *D = &EndN->getCodeDecl();
+
+ os << (isa<ObjCMethodDecl>(D) ? " is returned from a method "
+ : " is returned from a function ");
+
+ if (D->hasAttr<CFReturnsNotRetainedAttr>()) {
+ os << "that is annotated as CF_RETURNS_NOT_RETAINED";
+ } else if (D->hasAttr<NSReturnsNotRetainedAttr>()) {
+ os << "that is annotated as NS_RETURNS_NOT_RETAINED";
+ } else if (D->hasAttr<OSReturnsNotRetainedAttr>()) {
+ os << "that is annotated as OS_RETURNS_NOT_RETAINED";
+ } else {
+ if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) {
+ if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) {
+ os << "managed by Automatic Reference Counting";
+ } else {
+ os << "whose name ('" << MD->getSelector().getAsString()
+ << "') does not start with "
+ "'copy', 'mutableCopy', 'alloc' or 'new'."
+ " This violates the naming convention rules"
+ " given in the Memory Management Guide for Cocoa";
+ }
+ } else {
+ const FunctionDecl *FD = cast<FunctionDecl>(D);
+ os << "whose name ('" << *FD
+ << "') does not contain 'Copy' or 'Create'. This violates the naming"
+ " convention rules given in the Memory Management Guide for Core"
+ " Foundation";
+ }
+ }
+ } else {
+ os << " is not referenced later in this execution path and has a retain "
+ "count of +" << RV->getCount();
+ }
+
+ return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
+}
+
+RefCountReport::RefCountReport(RefCountBug &D, const LangOptions &LOpts,
+ ExplodedNode *n, SymbolRef sym,
+ bool registerVisitor)
+ : BugReport(D, D.getDescription(), n), Sym(sym) {
+ if (registerVisitor)
+ addVisitor(llvm::make_unique<RefCountReportVisitor>(sym));
+}
+
+RefCountReport::RefCountReport(RefCountBug &D, const LangOptions &LOpts,
+ ExplodedNode *n, SymbolRef sym,
+ StringRef endText)
+ : BugReport(D, D.getDescription(), endText, n) {
+
+ addVisitor(llvm::make_unique<RefCountReportVisitor>(sym));
+}
+
+void RefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) {
+ const SourceManager& SMgr = Ctx.getSourceManager();
+
+ if (!sym->getOriginRegion())
+ return;
+
+ auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion());
+ if (Region) {
+ const Decl *PDecl = Region->getDecl();
+ if (PDecl && isa<ParmVarDecl>(PDecl)) {
+ PathDiagnosticLocation ParamLocation =
+ PathDiagnosticLocation::create(PDecl, SMgr);
+ Location = ParamLocation;
+ UniqueingLocation = ParamLocation;
+ UniqueingDecl = Ctx.getLocationContext()->getDecl();
+ }
+ }
+}
+
+void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx,
+ SymbolRef sym) {
+ // Most bug reports are cached at the location where they occurred.
+ // With leaks, we want to unique them by the location where they were
+ // allocated, and only report a single path. To do this, we need to find
+ // the allocation site of a piece of tracked memory, which we do via a
+ // call to GetAllocationSite. This will walk the ExplodedGraph backwards.
+ // Note that this is *not* the trimmed graph; we are guaranteed, however,
+ // that all ancestor nodes that represent the allocation site have the
+ // same SourceLocation.
+ const ExplodedNode *AllocNode = nullptr;
+
+ const SourceManager& SMgr = Ctx.getSourceManager();
+
+ AllocationInfo AllocI =
+ GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym);
+
+ AllocNode = AllocI.N;
+ AllocBinding = AllocI.R;
+ markInteresting(AllocI.InterestingMethodContext);
+
+ // Get the SourceLocation for the allocation site.
+ // FIXME: This will crash the analyzer if an allocation comes from an
+ // implicit call (ex: a destructor call).
+ // (Currently there are no such allocations in Cocoa, though.)
+ AllocStmt = PathDiagnosticLocation::getStmt(AllocNode);
+
+ if (!AllocStmt) {
+ AllocBinding = nullptr;
+ return;
+ }
+
+ PathDiagnosticLocation AllocLocation =
+ PathDiagnosticLocation::createBegin(AllocStmt, SMgr,
+ AllocNode->getLocationContext());
+ Location = AllocLocation;
+
+ // Set uniqieing info, which will be used for unique the bug reports. The
+ // leaks should be uniqued on the allocation site.
+ UniqueingLocation = AllocLocation;
+ UniqueingDecl = AllocNode->getLocationContext()->getDecl();
+}
+
+void RefLeakReport::createDescription(CheckerContext &Ctx) {
+ assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid());
+ Description.clear();
+ llvm::raw_string_ostream os(Description);
+ os << "Potential leak of an object";
+
+ Optional<std::string> RegionDescription = describeRegion(AllocBinding);
+ if (RegionDescription) {
+ os << " stored into '" << *RegionDescription << '\'';
+ } else {
+
+ // If we can't figure out the name, just supply the type information.
+ os << " of type '" << getPrettyTypeName(Sym->getType()) << "'";
+ }
+}
+
+RefLeakReport::RefLeakReport(RefCountBug &D, const LangOptions &LOpts,
+ ExplodedNode *n, SymbolRef sym,
+ CheckerContext &Ctx)
+ : RefCountReport(D, LOpts, n, sym, false) {
+
+ deriveAllocLocation(Ctx, sym);
+ if (!AllocBinding)
+ deriveParamLocation(Ctx, sym);
+
+ createDescription(Ctx);
+
+ addVisitor(llvm::make_unique<RefLeakReportVisitor>(sym));
+}
diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h
new file mode 100644
index 000000000000..9f796abe8eae
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h
@@ -0,0 +1,85 @@
+//== RetainCountDiagnostics.h - Checks for leaks and other issues -*- 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 diagnostics for RetainCountChecker, which implements
+// a reference count checker for Core Foundation and Cocoa on (Mac OS X).
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H
+#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H
+
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
+#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h"
+
+namespace clang {
+namespace ento {
+namespace retaincountchecker {
+
+class RefCountBug : public BugType {
+protected:
+ RefCountBug(const CheckerBase *checker, StringRef name)
+ : BugType(checker, name, categories::MemoryRefCount) {}
+
+public:
+ virtual const char *getDescription() const = 0;
+
+ virtual bool isLeak() const { return false; }
+};
+
+class RefCountReport : public BugReport {
+protected:
+ SymbolRef Sym;
+
+public:
+ RefCountReport(RefCountBug &D, const LangOptions &LOpts,
+ ExplodedNode *n, SymbolRef sym,
+ bool registerVisitor = true);
+
+ RefCountReport(RefCountBug &D, const LangOptions &LOpts,
+ ExplodedNode *n, SymbolRef sym,
+ StringRef endText);
+
+ llvm::iterator_range<ranges_iterator> getRanges() override {
+ const RefCountBug& BugTy = static_cast<RefCountBug&>(getBugType());
+ if (!BugTy.isLeak())
+ return BugReport::getRanges();
+ return llvm::make_range(ranges_iterator(), ranges_iterator());
+ }
+};
+
+class RefLeakReport : public RefCountReport {
+ const MemRegion* AllocBinding;
+ const Stmt *AllocStmt;
+
+ // Finds the function declaration where a leak warning for the parameter
+ // 'sym' should be raised.
+ void deriveParamLocation(CheckerContext &Ctx, SymbolRef sym);
+ // Finds the location where a leak warning for 'sym' should be raised.
+ void deriveAllocLocation(CheckerContext &Ctx, SymbolRef sym);
+ // Produces description of a leak warning which is printed on the console.
+ void createDescription(CheckerContext &Ctx);
+
+public:
+ RefLeakReport(RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n,
+ SymbolRef sym, CheckerContext &Ctx);
+
+ PathDiagnosticLocation getLocation(const SourceManager &SM) const override {
+ assert(Location.isValid());
+ return Location;
+ }
+};
+
+} // end namespace retaincountchecker
+} // end namespace ento
+} // end namespace clang
+
+#endif
diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
index 1952715a9b7c..17ef39531628 100644
--- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
index c5e826a84b84..3e0613e8ba68 100644
--- a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp
@@ -13,7 +13,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -87,7 +87,7 @@ static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE,
auto Report = llvm::make_unique<BugReport>(BT, BT.getDescription(), N);
Report->addRange(RetE->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, TrackingE ? TrackingE : RetE, *Report);
+ bugreporter::trackExpressionValue(N, TrackingE ? TrackingE : RetE, *Report);
C.emitReport(std::move(Report));
}
diff --git a/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
index 55516a34d1a7..cf03b3c21132 100644
--- a/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp
@@ -23,7 +23,7 @@
//===----------------------------------------------------------------------===//
//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
@@ -58,13 +58,12 @@ public:
} // end anonymous namespace
-
-using TriBoolTy = Optional<bool>;
-using MemoizationMapTy = llvm::DenseMap<const Stmt *, Optional<TriBoolTy>>;
-
-static TriBoolTy
-seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B,
- MemoizationMapTy &Memoization) {
+/// \return Whether {@code A} occurs before {@code B} in traversal of
+/// {@code Parent}.
+/// Conceptually a very incomplete/unsound approximation of happens-before
+/// relationship (A is likely to be evaluated before B),
+/// but useful enough in this case.
+static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) {
for (const Stmt *C : Parent->children()) {
if (!C) continue;
@@ -74,26 +73,9 @@ seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B,
if (C == B)
return false;
- Optional<TriBoolTy> &Cached = Memoization[C];
- if (!Cached)
- Cached = seenBeforeRec(C, A, B, Memoization);
-
- if (Cached->hasValue())
- return Cached->getValue();
+ return seenBefore(C, A, B);
}
-
- return None;
-}
-
-/// \return Whether {@code A} occurs before {@code B} in traversal of
-/// {@code Parent}.
-/// Conceptually a very incomplete/unsound approximation of happens-before
-/// relationship (A is likely to be evaluated before B),
-/// but useful enough in this case.
-static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) {
- MemoizationMapTy Memoization;
- TriBoolTy Val = seenBeforeRec(Parent, A, B, Memoization);
- return Val.getValue();
+ return false;
}
static void emitDiagnostics(BoundNodes &Match,
diff --git a/lib/StaticAnalyzer/Checkers/SelectorExtras.h b/lib/StaticAnalyzer/Checkers/SelectorExtras.h
deleted file mode 100644
index b11d070c629b..000000000000
--- a/lib/StaticAnalyzer/Checkers/SelectorExtras.h
+++ /dev/null
@@ -1,46 +0,0 @@
-//=== SelectorExtras.h - Helpers for checkers using selectors -----*- C++ -*-=//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SELECTOREXTRAS_H
-#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SELECTOREXTRAS_H
-
-#include "clang/AST/ASTContext.h"
-
-namespace clang {
-namespace ento {
-
-template <typename... IdentifierInfos>
-static inline Selector getKeywordSelector(ASTContext &Ctx,
- IdentifierInfos *... IIs) {
- static_assert(sizeof...(IdentifierInfos),
- "keyword selectors must have at least one argument");
- SmallVector<IdentifierInfo *, 10> II({&Ctx.Idents.get(IIs)...});
-
- return Ctx.Selectors.getSelector(II.size(), &II[0]);
-}
-
-template <typename... IdentifierInfos>
-static inline void lazyInitKeywordSelector(Selector &Sel, ASTContext &Ctx,
- IdentifierInfos *... IIs) {
- if (!Sel.isNull())
- return;
- Sel = getKeywordSelector(Ctx, IIs...);
-}
-
-static inline void lazyInitNullarySelector(Selector &Sel, ASTContext &Ctx,
- const char *Name) {
- if (!Sel.isNull())
- return;
- Sel = GetNullarySelector(Name, Ctx);
-}
-
-} // end namespace ento
-} // end namespace clang
-
-#endif
diff --git a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
index ab4b4d3bd91b..819d437e6883 100644
--- a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp
@@ -15,7 +15,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
index e7a20fa03a4a..0f53d826a5f6 100644
--- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Basic/SourceManager.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -79,17 +79,17 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R,
const CompoundLiteralExpr *CL = CR->getLiteralExpr();
os << "stack memory associated with a compound literal "
"declared on line "
- << SM.getExpansionLineNumber(CL->getLocStart()) << " returned to caller";
+ << SM.getExpansionLineNumber(CL->getBeginLoc()) << " returned to caller";
range = CL->getSourceRange();
} else if (const auto *AR = dyn_cast<AllocaRegion>(R)) {
const Expr *ARE = AR->getExpr();
- SourceLocation L = ARE->getLocStart();
+ SourceLocation L = ARE->getBeginLoc();
range = ARE->getSourceRange();
os << "stack memory allocated by call to alloca() on line "
<< SM.getExpansionLineNumber(L);
} else if (const auto *BR = dyn_cast<BlockDataRegion>(R)) {
const BlockDecl *BD = BR->getCodeRegion()->getDecl();
- SourceLocation L = BD->getLocStart();
+ SourceLocation L = BD->getBeginLoc();
range = BD->getSourceRange();
os << "stack-allocated block declared on line "
<< SM.getExpansionLineNumber(L);
diff --git a/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
index 2f9f5d2d9cf8..6478128ce954 100644
--- a/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
@@ -51,7 +51,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
index d77975559e3f..92647f032730 100644
--- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -383,26 +383,26 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE,
void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
+ ProgramStateRef state = C.getState();
+
// TODO: Clean up the state.
- for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(),
- E = SymReaper.dead_end(); I != E; ++I) {
- SymbolRef Sym = *I;
- ProgramStateRef state = C.getState();
- const StreamState *SS = state->get<StreamMap>(Sym);
- if (!SS)
+ const StreamMapTy &Map = state->get<StreamMap>();
+ for (const auto &I: Map) {
+ SymbolRef Sym = I.first;
+ const StreamState &SS = I.second;
+ if (!SymReaper.isDead(Sym) || !SS.isOpened())
continue;
- if (SS->isOpened()) {
- ExplodedNode *N = C.generateErrorNode();
- if (N) {
- if (!BT_ResourceLeak)
- BT_ResourceLeak.reset(new BuiltinBug(
- this, "Resource Leak",
- "Opened File never closed. Potential Resource leak."));
- C.emitReport(llvm::make_unique<BugReport>(
- *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
- }
- }
+ ExplodedNode *N = C.generateErrorNode();
+ if (!N)
+ return;
+
+ if (!BT_ResourceLeak)
+ BT_ResourceLeak.reset(
+ new BuiltinBug(this, "Resource Leak",
+ "Opened File never closed. Potential Resource leak."));
+ C.emitReport(llvm::make_unique<BugReport>(
+ *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N));
}
}
diff --git a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
index 2e0529015ca6..3aa8e95d0ad0 100644
--- a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp
@@ -10,7 +10,7 @@
// This checker can be used for testing how taint data is propagated.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
diff --git a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
index f4c0edbab3f0..527e371571f1 100644
--- a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
@@ -71,7 +71,6 @@ public:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC,
BugReport &BR) override;
};
@@ -95,7 +94,7 @@ public:
REGISTER_SET_WITH_PROGRAMSTATE(DivZeroMap, ZeroState)
std::shared_ptr<PathDiagnosticPiece>
-DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, const ExplodedNode *Pred,
+DivisionBRVisitor::VisitNode(const ExplodedNode *Succ,
BugReporterContext &BRC, BugReport &BR) {
if (Satisfied)
return nullptr;
@@ -180,7 +179,7 @@ void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const {
}
}
-void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *RS,
+void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
diff --git a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp
index ee185b813611..2f06469bb209 100644
--- a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp
@@ -11,7 +11,7 @@
// as it builds the ExplodedGraph.
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/AST/StmtObjC.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -47,7 +47,7 @@ void TraversalDumper::checkBranchCondition(const Stmt *Condition,
// It is mildly evil to print directly to llvm::outs() rather than emitting
// warnings, but this ensures things do not get filtered out by the rest of
// the static analyzer machinery.
- SourceLocation Loc = Parent->getLocStart();
+ SourceLocation Loc = Parent->getBeginLoc();
llvm::outs() << C.getSourceManager().getSpellingLineNumber(Loc) << " "
<< Parent->getStmtClassName() << "\n";
}
diff --git a/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp
index f3d68014224d..5e777803af00 100644
--- a/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp
@@ -1,4 +1,4 @@
-//== TrustNonnullChecker.cpp - Checker for trusting annotations -*- C++ -*--==//
+//== TrustNonnullChecker.cpp --------- API nullability modeling -*- C++ -*--==//
//
// The LLVM Compiler Infrastructure
//
@@ -7,12 +7,20 @@
//
//===----------------------------------------------------------------------===//
//
-// This checker adds an assumption that methods annotated with _Nonnull
+// This checker adds nullability-related assumptions:
+//
+// 1. Methods annotated with _Nonnull
// which come from system headers actually return a non-null pointer.
//
+// 2. NSDictionary key is non-null after the keyword subscript operation
+// on read if and only if the resulting expression is non-null.
+//
+// 3. NSMutableDictionary index is non-null after a write operation.
+//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/Analysis/SelectorExtras.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
@@ -22,10 +30,129 @@
using namespace clang;
using namespace ento;
+/// Records implications between symbols.
+/// The semantics is:
+/// (antecedent != 0) => (consequent != 0)
+/// These implications are then read during the evaluation of the assumption,
+/// and the appropriate antecedents are applied.
+REGISTER_MAP_WITH_PROGRAMSTATE(NonNullImplicationMap, SymbolRef, SymbolRef)
+
+/// The semantics is:
+/// (antecedent == 0) => (consequent == 0)
+REGISTER_MAP_WITH_PROGRAMSTATE(NullImplicationMap, SymbolRef, SymbolRef)
+
namespace {
-class TrustNonnullChecker : public Checker<check::PostCall> {
+class TrustNonnullChecker : public Checker<check::PostCall,
+ check::PostObjCMessage,
+ check::DeadSymbols,
+ eval::Assume> {
+ // Do not try to iterate over symbols with higher complexity.
+ static unsigned constexpr ComplexityThreshold = 10;
+ Selector ObjectForKeyedSubscriptSel;
+ Selector ObjectForKeySel;
+ Selector SetObjectForKeyedSubscriptSel;
+ Selector SetObjectForKeySel;
+
+public:
+ TrustNonnullChecker(ASTContext &Ctx)
+ : ObjectForKeyedSubscriptSel(
+ getKeywordSelector(Ctx, "objectForKeyedSubscript")),
+ ObjectForKeySel(getKeywordSelector(Ctx, "objectForKey")),
+ SetObjectForKeyedSubscriptSel(
+ getKeywordSelector(Ctx, "setObject", "forKeyedSubscript")),
+ SetObjectForKeySel(getKeywordSelector(Ctx, "setObject", "forKey")) {}
+
+ ProgramStateRef evalAssume(ProgramStateRef State,
+ SVal Cond,
+ bool Assumption) const {
+ const SymbolRef CondS = Cond.getAsSymbol();
+ if (!CondS || CondS->computeComplexity() > ComplexityThreshold)
+ return State;
+
+ for (auto B=CondS->symbol_begin(), E=CondS->symbol_end(); B != E; ++B) {
+ const SymbolRef Antecedent = *B;
+ State = addImplication(Antecedent, State, true);
+ State = addImplication(Antecedent, State, false);
+ }
+
+ return State;
+ }
+
+ void checkPostCall(const CallEvent &Call, CheckerContext &C) const {
+ // Only trust annotations for system headers for non-protocols.
+ if (!Call.isInSystemHeader())
+ return;
+
+ ProgramStateRef State = C.getState();
+
+ if (isNonNullPtr(Call, C))
+ if (auto L = Call.getReturnValue().getAs<Loc>())
+ State = State->assume(*L, /*Assumption=*/true);
+
+ C.addTransition(State);
+ }
+
+ void checkPostObjCMessage(const ObjCMethodCall &Msg,
+ CheckerContext &C) const {
+ const ObjCInterfaceDecl *ID = Msg.getReceiverInterface();
+ if (!ID)
+ return;
+
+ ProgramStateRef State = C.getState();
+
+ // Index to setter for NSMutableDictionary is assumed to be non-null,
+ // as an exception is thrown otherwise.
+ if (interfaceHasSuperclass(ID, "NSMutableDictionary") &&
+ (Msg.getSelector() == SetObjectForKeyedSubscriptSel ||
+ Msg.getSelector() == SetObjectForKeySel)) {
+ if (auto L = Msg.getArgSVal(1).getAs<Loc>())
+ State = State->assume(*L, /*Assumption=*/true);
+ }
+
+ // Record an implication: index is non-null if the output is non-null.
+ if (interfaceHasSuperclass(ID, "NSDictionary") &&
+ (Msg.getSelector() == ObjectForKeyedSubscriptSel ||
+ Msg.getSelector() == ObjectForKeySel)) {
+ SymbolRef ArgS = Msg.getArgSVal(0).getAsSymbol();
+ SymbolRef RetS = Msg.getReturnValue().getAsSymbol();
+
+ if (ArgS && RetS) {
+ // Emulate an implication: the argument is non-null if
+ // the return value is non-null.
+ State = State->set<NonNullImplicationMap>(RetS, ArgS);
+
+ // Conversely, when the argument is null, the return value
+ // is definitely null.
+ State = State->set<NullImplicationMap>(ArgS, RetS);
+ }
+ }
+
+ C.addTransition(State);
+ }
+
+ void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+
+ State = dropDeadFromGDM<NullImplicationMap>(SymReaper, State);
+ State = dropDeadFromGDM<NonNullImplicationMap>(SymReaper, State);
+
+ C.addTransition(State);
+ }
+
private:
+
+ /// \returns State with GDM \p MapName where all dead symbols were
+ // removed.
+ template <typename MapName>
+ ProgramStateRef dropDeadFromGDM(SymbolReaper &SymReaper,
+ ProgramStateRef State) const {
+ for (const std::pair<SymbolRef, SymbolRef> &P : State->get<MapName>())
+ if (!SymReaper.isLive(P.first) || !SymReaper.isLive(P.second))
+ State = State->remove<MapName>(P.first);
+ return State;
+ }
+
/// \returns Whether we trust the result of the method call to be
/// a non-null pointer.
bool isNonNullPtr(const CallEvent &Call, CheckerContext &C) const {
@@ -66,19 +193,57 @@ private:
return false;
}
-public:
- void checkPostCall(const CallEvent &Call, CheckerContext &C) const {
- // Only trust annotations for system headers for non-protocols.
- if (!Call.isInSystemHeader())
- return;
+ /// \return Whether \p ID has a superclass by the name \p ClassName.
+ bool interfaceHasSuperclass(const ObjCInterfaceDecl *ID,
+ StringRef ClassName) const {
+ if (ID->getIdentifier()->getName() == ClassName)
+ return true;
- ProgramStateRef State = C.getState();
+ if (const ObjCInterfaceDecl *Super = ID->getSuperClass())
+ return interfaceHasSuperclass(Super, ClassName);
- if (isNonNullPtr(Call, C))
- if (auto L = Call.getReturnValue().getAs<Loc>())
- State = State->assume(*L, /*Assumption=*/true);
+ return false;
+ }
- C.addTransition(State);
+
+ /// \return a state with an optional implication added (if exists)
+ /// from a map of recorded implications.
+ /// If \p Negated is true, checks NullImplicationMap, and assumes
+ /// the negation of \p Antecedent.
+ /// Checks NonNullImplicationMap and assumes \p Antecedent otherwise.
+ ProgramStateRef addImplication(SymbolRef Antecedent,
+ ProgramStateRef InputState,
+ bool Negated) const {
+ if (!InputState)
+ return nullptr;
+ SValBuilder &SVB = InputState->getStateManager().getSValBuilder();
+ const SymbolRef *Consequent =
+ Negated ? InputState->get<NonNullImplicationMap>(Antecedent)
+ : InputState->get<NullImplicationMap>(Antecedent);
+ if (!Consequent)
+ return InputState;
+
+ SVal AntecedentV = SVB.makeSymbolVal(Antecedent);
+ ProgramStateRef State = InputState;
+
+ if ((Negated && InputState->isNonNull(AntecedentV).isConstrainedTrue())
+ || (!Negated && InputState->isNull(AntecedentV).isConstrainedTrue())) {
+ SVal ConsequentS = SVB.makeSymbolVal(*Consequent);
+ State = InputState->assume(ConsequentS.castAs<DefinedSVal>(), Negated);
+ if (!State)
+ return nullptr;
+
+ // Drop implications from the map.
+ if (Negated) {
+ State = State->remove<NonNullImplicationMap>(Antecedent);
+ State = State->remove<NullImplicationMap>(*Consequent);
+ } else {
+ State = State->remove<NullImplicationMap>(Antecedent);
+ State = State->remove<NonNullImplicationMap>(*Consequent);
+ }
+ }
+
+ return State;
}
};
@@ -86,5 +251,5 @@ public:
void ento::registerTrustNonnullChecker(CheckerManager &Mgr) {
- Mgr.registerChecker<TrustNonnullChecker>();
+ Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext());
}
diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
index 934ee63318fa..d7fad4e475ab 100644
--- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -98,7 +98,7 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition,
// Emit the bug report.
auto R = llvm::make_unique<BugReport>(*BT, BT->getDescription(), N);
- bugreporter::trackNullOrUndefValue(N, Ex, *R);
+ bugreporter::trackExpressionValue(N, Ex, *R);
R->addRange(Ex->getSourceRange());
Ctx.emitReport(std::move(R));
diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
index 6a93c10c7644..8a625227b81e 100644
--- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp
@@ -11,7 +11,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/Attr.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
index b9a93bedca2e..624cff6048fd 100644
--- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -69,6 +69,7 @@ static bool isLeftShiftResultUnrepresentable(const BinaryOperator *B,
ProgramStateRef State = C.getState();
const llvm::APSInt *LHS = SB.getKnownValue(State, C.getSVal(B->getLHS()));
const llvm::APSInt *RHS = SB.getKnownValue(State, C.getSVal(B->getRHS()));
+ assert(LHS && RHS && "Values unknown, inconsistent state");
return (unsigned)RHS->getZExtValue() > LHS->countLeadingZeros();
}
@@ -122,6 +123,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
<< ((B->getOpcode() == BinaryOperatorKind::BO_Shl) ? "left"
: "right")
<< " shift is undefined because the right operand is negative";
+ Ex = B->getRHS();
} else if ((B->getOpcode() == BinaryOperatorKind::BO_Shl ||
B->getOpcode() == BinaryOperatorKind::BO_Shr) &&
isShiftOverflow(B, C)) {
@@ -130,6 +132,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
<< ((B->getOpcode() == BinaryOperatorKind::BO_Shl) ? "left"
: "right")
<< " shift is undefined due to shifting by ";
+ Ex = B->getRHS();
SValBuilder &SB = C.getSValBuilder();
const llvm::APSInt *I =
@@ -147,6 +150,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
C.isNegative(B->getLHS())) {
OS << "The result of the left shift is undefined because the left "
"operand is negative";
+ Ex = B->getLHS();
} else if (B->getOpcode() == BinaryOperatorKind::BO_Shl &&
isLeftShiftResultUnrepresentable(B, C)) {
ProgramStateRef State = C.getState();
@@ -160,6 +164,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
<< "\', which is unrepresentable in the unsigned version of "
<< "the return type \'" << B->getLHS()->getType().getAsString()
<< "\'";
+ Ex = B->getLHS();
} else {
OS << "The result of the '"
<< BinaryOperator::getOpcodeStr(B->getOpcode())
@@ -169,10 +174,10 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B,
auto report = llvm::make_unique<BugReport>(*BT, OS.str(), N);
if (Ex) {
report->addRange(Ex->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, Ex, *report);
+ bugreporter::trackExpressionValue(N, Ex, *report);
}
else
- bugreporter::trackNullOrUndefValue(N, B, *report);
+ bugreporter::trackExpressionValue(N, B, *report);
C.emitReport(std::move(report));
}
diff --git a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
index fe07eafd281f..1d78d7cebd67 100644
--- a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclCXX.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -55,7 +55,7 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A,
// Generate a report for this bug.
auto R = llvm::make_unique<BugReport>(*BT, BT->getName(), N);
R->addRange(A->getIdx()->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, A->getIdx(), *R);
+ bugreporter::trackExpressionValue(N, A->getIdx(), *R);
C.emitReport(std::move(R));
}
diff --git a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
index 2ef6855ba6b7..8e10bfdd2f3c 100644
--- a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -112,7 +112,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val,
auto R = llvm::make_unique<BugReport>(*BT, OS.str(), N);
if (ex) {
R->addRange(ex->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, ex, *R);
+ bugreporter::trackExpressionValue(N, ex, *R);
}
C.emitReport(std::move(R));
}
diff --git a/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h
new file mode 100644
index 000000000000..c3291a21c164
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObject.h
@@ -0,0 +1,349 @@
+//===----- UninitializedObject.h ---------------------------------*- 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 helper classes for UninitializedObjectChecker and
+// documentation about the logic of it.
+//
+// The checker reports uninitialized fields in objects created after a
+// constructor call.
+//
+// This checker has several options:
+// - "Pedantic" (boolean). If its not set or is set to false, the checker
+// won't emit warnings for objects that don't have at least one initialized
+// field. This may be set with
+//
+// `-analyzer-config alpha.cplusplus.UninitializedObject:Pedantic=true`.
+//
+// - "NotesAsWarnings" (boolean). If set to true, the checker will emit a
+// warning for each uninitialized field, as opposed to emitting one warning
+// per constructor call, and listing the uninitialized fields that belongs
+// to it in notes. Defaults to false.
+//
+// `-analyzer-config \
+// alpha.cplusplus.UninitializedObject:NotesAsWarnings=true`.
+//
+// - "CheckPointeeInitialization" (boolean). If set to false, the checker will
+// not analyze the pointee of pointer/reference fields, and will only check
+// whether the object itself is initialized. Defaults to false.
+//
+// `-analyzer-config \
+// alpha.cplusplus.UninitializedObject:CheckPointeeInitialization=true`.
+//
+// - "IgnoreRecordsWithField" (string). If supplied, the checker will not
+// analyze structures that have a field with a name or type name that
+// matches the given pattern. Defaults to "".
+//
+// `-analyzer-config \
+// alpha.cplusplus.UninitializedObject:IgnoreRecordsWithField="[Tt]ag|[Kk]ind"`.
+//
+// TODO: With some clever heuristics, some pointers should be dereferenced
+// by default. For example, if the pointee is constructed within the
+// constructor call, it's reasonable to say that no external object
+// references it, and we wouldn't generate multiple report on the same
+// pointee.
+//
+// Most of the following methods as well as the checker itself is defined in
+// UninitializedObjectChecker.cpp.
+//
+// Some methods are implemented in UninitializedPointee.cpp, to reduce the
+// complexity of the main checker file.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_STATICANALYZER_UNINITIALIZEDOBJECT_H
+#define LLVM_CLANG_STATICANALYZER_UNINITIALIZEDOBJECT_H
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+
+namespace clang {
+namespace ento {
+
+struct UninitObjCheckerOptions {
+ bool IsPedantic = false;
+ bool ShouldConvertNotesToWarnings = false;
+ bool CheckPointeeInitialization = false;
+ std::string IgnoredRecordsWithFieldPattern;
+};
+
+/// A lightweight polymorphic wrapper around FieldRegion *. We'll use this
+/// interface to store addinitional information about fields. As described
+/// later, a list of these objects (i.e. "fieldchain") will be constructed and
+/// used for printing note messages should an uninitialized value be found.
+class FieldNode {
+protected:
+ const FieldRegion *FR;
+
+ /// FieldNodes are never meant to be created on the heap, see
+ /// FindUninitializedFields::addFieldToUninits().
+ /* non-virtual */ ~FieldNode() = default;
+
+public:
+ FieldNode(const FieldRegion *FR) : FR(FR) {}
+
+ // We'll delete all of these special member functions to force the users of
+ // this interface to only store references to FieldNode objects in containers.
+ FieldNode() = delete;
+ FieldNode(const FieldNode &) = delete;
+ FieldNode(FieldNode &&) = delete;
+ FieldNode &operator=(const FieldNode &) = delete;
+ FieldNode &operator=(const FieldNode &&) = delete;
+
+ void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(this); }
+
+ /// Helper method for uniqueing.
+ bool isSameRegion(const FieldRegion *OtherFR) const {
+ // Special FieldNode descendants may wrap nullpointers (for example if they
+ // describe a special relationship between two elements of the fieldchain)
+ // -- we wouldn't like to unique these objects.
+ if (FR == nullptr)
+ return false;
+
+ return FR == OtherFR;
+ }
+
+ const FieldRegion *getRegion() const { return FR; }
+ const FieldDecl *getDecl() const {
+ assert(FR);
+ return FR->getDecl();
+ }
+
+ // When a fieldchain is printed, it will have the following format (without
+ // newline, indices are in order of insertion, from 1 to n):
+ //
+ // <note_message_n>'<prefix_n><prefix_n-1>...<prefix_1>
+ // this-><node_1><separator_1><node_2><separator_2>...<node_n>'
+
+ /// If this is the last element of the fieldchain, this method will print the
+ /// note message associated with it.
+ /// The note message should state something like "uninitialized field" or
+ /// "uninitialized pointee" etc.
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const = 0;
+
+ /// Print any prefixes before the fieldchain. Could contain casts, etc.
+ virtual void printPrefix(llvm::raw_ostream &Out) const = 0;
+
+ /// Print the node. Should contain the name of the field stored in FR.
+ virtual void printNode(llvm::raw_ostream &Out) const = 0;
+
+ /// Print the separator. For example, fields may be separated with '.' or
+ /// "->".
+ virtual void printSeparator(llvm::raw_ostream &Out) const = 0;
+
+ virtual bool isBase() const { return false; }
+};
+
+/// Returns with Field's name. This is a helper function to get the correct name
+/// even if Field is a captured lambda variable.
+std::string getVariableName(const FieldDecl *Field);
+
+/// Represents a field chain. A field chain is a list of fields where the first
+/// element of the chain is the object under checking (not stored), and every
+/// other element is a field, and the element that precedes it is the object
+/// that contains it.
+///
+/// Note that this class is immutable (essentially a wrapper around an
+/// ImmutableList), new FieldChainInfo objects may be created by member
+/// functions such as add() and replaceHead().
+class FieldChainInfo {
+public:
+ using FieldChain = llvm::ImmutableList<const FieldNode &>;
+
+private:
+ FieldChain::Factory &ChainFactory;
+ FieldChain Chain;
+
+ FieldChainInfo(FieldChain::Factory &F, FieldChain NewChain)
+ : FieldChainInfo(F) {
+ Chain = NewChain;
+ }
+
+public:
+ FieldChainInfo() = delete;
+ FieldChainInfo(FieldChain::Factory &F) : ChainFactory(F) {}
+ FieldChainInfo(const FieldChainInfo &Other) = default;
+
+ /// Constructs a new FieldChainInfo object with \p FN appended.
+ template <class FieldNodeT> FieldChainInfo add(const FieldNodeT &FN);
+
+ /// Constructs a new FieldChainInfo object with \p FN as the new head of the
+ /// list.
+ template <class FieldNodeT> FieldChainInfo replaceHead(const FieldNodeT &FN);
+
+ bool contains(const FieldRegion *FR) const;
+ bool isEmpty() const { return Chain.isEmpty(); }
+
+ const FieldNode &getHead() const { return Chain.getHead(); }
+ const FieldRegion *getUninitRegion() const { return getHead().getRegion(); }
+
+ void printNoteMsg(llvm::raw_ostream &Out) const;
+};
+
+using UninitFieldMap = std::map<const FieldRegion *, llvm::SmallString<50>>;
+
+/// Searches for and stores uninitialized fields in a non-union object.
+class FindUninitializedFields {
+ ProgramStateRef State;
+ const TypedValueRegion *const ObjectR;
+
+ const UninitObjCheckerOptions Opts;
+ bool IsAnyFieldInitialized = false;
+
+ FieldChainInfo::FieldChain::Factory ChainFactory;
+
+ /// A map for assigning uninitialized regions to note messages. For example,
+ ///
+ /// struct A {
+ /// int x;
+ /// };
+ ///
+ /// A a;
+ ///
+ /// After analyzing `a`, the map will contain a pair for `a.x`'s region and
+ /// the note message "uninitialized field 'this->x'.
+ UninitFieldMap UninitFields;
+
+public:
+ /// Constructs the FindUninitializedField object, searches for and stores
+ /// uninitialized fields in R.
+ FindUninitializedFields(ProgramStateRef State,
+ const TypedValueRegion *const R,
+ const UninitObjCheckerOptions &Opts);
+
+ /// Returns with the modified state and a map of (uninitialized region,
+ /// note message) pairs.
+ std::pair<ProgramStateRef, const UninitFieldMap &> getResults() {
+ return {State, UninitFields};
+ }
+
+ /// Returns whether the analyzed region contains at least one initialized
+ /// field. Note that this includes subfields as well, not just direct ones,
+ /// and will return false if an uninitialized pointee is found with
+ /// CheckPointeeInitialization enabled.
+ bool isAnyFieldInitialized() { return IsAnyFieldInitialized; }
+
+private:
+ // For the purposes of this checker, we'll regard the analyzed region as a
+ // directed tree, where
+ // * the root is the object under checking
+ // * every node is an object that is
+ // - a union
+ // - a non-union record
+ // - dereferenceable (see isDereferencableType())
+ // - an array
+ // - of a primitive type (see isPrimitiveType())
+ // * the parent of each node is the object that contains it
+ // * every leaf is an array, a primitive object, a nullptr or an undefined
+ // pointer.
+ //
+ // Example:
+ //
+ // struct A {
+ // struct B {
+ // int x, y = 0;
+ // };
+ // B b;
+ // int *iptr = new int;
+ // B* bptr;
+ //
+ // A() {}
+ // };
+ //
+ // The directed tree:
+ //
+ // ->x
+ // /
+ // ->b--->y
+ // /
+ // A-->iptr->(int value)
+ // \
+ // ->bptr
+ //
+ // From this we'll construct a vector of fieldchains, where each fieldchain
+ // represents an uninitialized field. An uninitialized field may be a
+ // primitive object, a pointer, a pointee or a union without a single
+ // initialized field.
+ // In the above example, for the default constructor call we'll end up with
+ // these fieldchains:
+ //
+ // this->b.x
+ // this->iptr (pointee uninit)
+ // this->bptr (pointer uninit)
+ //
+ // We'll traverse each node of the above graph with the appropriate one of
+ // these methods:
+
+ /// Checks the region of a union object, and returns true if no field is
+ /// initialized within the region.
+ bool isUnionUninit(const TypedValueRegion *R);
+
+ /// Checks a region of a non-union object, and returns true if an
+ /// uninitialized field is found within the region.
+ bool isNonUnionUninit(const TypedValueRegion *R, FieldChainInfo LocalChain);
+
+ /// Checks a region of a pointer or reference object, and returns true if the
+ /// ptr/ref object itself or any field within the pointee's region is
+ /// uninitialized.
+ bool isDereferencableUninit(const FieldRegion *FR, FieldChainInfo LocalChain);
+
+ /// Returns true if the value of a primitive object is uninitialized.
+ bool isPrimitiveUninit(const SVal &V);
+
+ // Note that we don't have a method for arrays -- the elements of an array are
+ // often left uninitialized intentionally even when it is of a C++ record
+ // type, so we'll assume that an array is always initialized.
+ // TODO: Add a support for nonloc::LocAsInteger.
+
+ /// Processes LocalChain and attempts to insert it into UninitFields. Returns
+ /// true on success. Also adds the head of the list and \p PointeeR (if
+ /// supplied) to the GDM as already analyzed objects.
+ ///
+ /// Since this class analyzes regions with recursion, we'll only store
+ /// references to temporary FieldNode objects created on the stack. This means
+ /// that after analyzing a leaf of the directed tree described above, the
+ /// elements LocalChain references will be destructed, so we can't store it
+ /// directly.
+ bool addFieldToUninits(FieldChainInfo LocalChain,
+ const MemRegion *PointeeR = nullptr);
+};
+
+/// Returns true if T is a primitive type. An object of a primitive type only
+/// needs to be analyzed as much as checking whether their value is undefined.
+inline bool isPrimitiveType(const QualType &T) {
+ return T->isBuiltinType() || T->isEnumeralType() ||
+ T->isMemberPointerType() || T->isBlockPointerType() ||
+ T->isFunctionType();
+}
+
+inline bool isDereferencableType(const QualType &T) {
+ return T->isAnyPointerType() || T->isReferenceType();
+}
+
+// Template method definitions.
+
+template <class FieldNodeT>
+inline FieldChainInfo FieldChainInfo::add(const FieldNodeT &FN) {
+ assert(!contains(FN.getRegion()) &&
+ "Can't add a field that is already a part of the "
+ "fieldchain! Is this a cyclic reference?");
+
+ FieldChainInfo NewChain = *this;
+ NewChain.Chain = ChainFactory.add(FN, Chain);
+ return NewChain;
+}
+
+template <class FieldNodeT>
+inline FieldChainInfo FieldChainInfo::replaceHead(const FieldNodeT &FN) {
+ FieldChainInfo NewChain(ChainFactory, Chain.getTail());
+ return NewChain.add(FN);
+}
+
+} // end of namespace ento
+} // end of namespace clang
+
+#endif // LLVM_CLANG_STATICANALYZER_UNINITIALIZEDOBJECT_H
diff --git a/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp
new file mode 100644
index 000000000000..208e303e8295
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp
@@ -0,0 +1,538 @@
+//===----- UninitializedObjectChecker.cpp ------------------------*- 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 a checker that reports uninitialized fields in objects
+// created after a constructor call.
+//
+// To read about command line options and how the checker works, refer to the
+// top of the file and inline comments in UninitializedObject.h.
+//
+// Some of the logic is implemented in UninitializedPointee.cpp, to reduce the
+// complexity of this file.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "UninitializedObject.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
+
+using namespace clang;
+using namespace clang::ento;
+
+/// We'll mark fields (and pointee of fields) that are confirmed to be
+/// uninitialized as already analyzed.
+REGISTER_SET_WITH_PROGRAMSTATE(AnalyzedRegions, const MemRegion *)
+
+namespace {
+
+class UninitializedObjectChecker
+ : public Checker<check::EndFunction, check::DeadSymbols> {
+ std::unique_ptr<BuiltinBug> BT_uninitField;
+
+public:
+ // The fields of this struct will be initialized when registering the checker.
+ UninitObjCheckerOptions Opts;
+
+ UninitializedObjectChecker()
+ : BT_uninitField(new BuiltinBug(this, "Uninitialized fields")) {}
+
+ void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
+ void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const;
+};
+
+/// A basic field type, that is not a pointer or a reference, it's dynamic and
+/// static type is the same.
+class RegularField final : public FieldNode {
+public:
+ RegularField(const FieldRegion *FR) : FieldNode(FR) {}
+
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const override {
+ Out << "uninitialized field ";
+ }
+
+ virtual void printPrefix(llvm::raw_ostream &Out) const override {}
+
+ virtual void printNode(llvm::raw_ostream &Out) const override {
+ Out << getVariableName(getDecl());
+ }
+
+ virtual void printSeparator(llvm::raw_ostream &Out) const override {
+ Out << '.';
+ }
+};
+
+/// Represents that the FieldNode that comes after this is declared in a base
+/// of the previous FieldNode. As such, this descendant doesn't wrap a
+/// FieldRegion, and is purely a tool to describe a relation between two other
+/// FieldRegion wrapping descendants.
+class BaseClass final : public FieldNode {
+ const QualType BaseClassT;
+
+public:
+ BaseClass(const QualType &T) : FieldNode(nullptr), BaseClassT(T) {
+ assert(!T.isNull());
+ assert(T->getAsCXXRecordDecl());
+ }
+
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const override {
+ llvm_unreachable("This node can never be the final node in the "
+ "fieldchain!");
+ }
+
+ virtual void printPrefix(llvm::raw_ostream &Out) const override {}
+
+ virtual void printNode(llvm::raw_ostream &Out) const override {
+ Out << BaseClassT->getAsCXXRecordDecl()->getName() << "::";
+ }
+
+ virtual void printSeparator(llvm::raw_ostream &Out) const override {}
+
+ virtual bool isBase() const override { return true; }
+};
+
+} // end of anonymous namespace
+
+// Utility function declarations.
+
+/// Returns the region that was constructed by CtorDecl, or nullptr if that
+/// isn't possible.
+static const TypedValueRegion *
+getConstructedRegion(const CXXConstructorDecl *CtorDecl,
+ CheckerContext &Context);
+
+/// Checks whether the object constructed by \p Ctor will be analyzed later
+/// (e.g. if the object is a field of another object, in which case we'd check
+/// it multiple times).
+static bool willObjectBeAnalyzedLater(const CXXConstructorDecl *Ctor,
+ CheckerContext &Context);
+
+/// Checks whether RD contains a field with a name or type name that matches
+/// \p Pattern.
+static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern);
+
+//===----------------------------------------------------------------------===//
+// Methods for UninitializedObjectChecker.
+//===----------------------------------------------------------------------===//
+
+void UninitializedObjectChecker::checkEndFunction(
+ const ReturnStmt *RS, CheckerContext &Context) const {
+
+ const auto *CtorDecl = dyn_cast_or_null<CXXConstructorDecl>(
+ Context.getLocationContext()->getDecl());
+ if (!CtorDecl)
+ return;
+
+ if (!CtorDecl->isUserProvided())
+ return;
+
+ if (CtorDecl->getParent()->isUnion())
+ return;
+
+ // This avoids essentially the same error being reported multiple times.
+ if (willObjectBeAnalyzedLater(CtorDecl, Context))
+ return;
+
+ const TypedValueRegion *R = getConstructedRegion(CtorDecl, Context);
+ if (!R)
+ return;
+
+ FindUninitializedFields F(Context.getState(), R, Opts);
+
+ std::pair<ProgramStateRef, const UninitFieldMap &> UninitInfo =
+ F.getResults();
+
+ ProgramStateRef UpdatedState = UninitInfo.first;
+ const UninitFieldMap &UninitFields = UninitInfo.second;
+
+ if (UninitFields.empty()) {
+ Context.addTransition(UpdatedState);
+ return;
+ }
+
+ // There are uninitialized fields in the record.
+
+ ExplodedNode *Node = Context.generateNonFatalErrorNode(UpdatedState);
+ if (!Node)
+ return;
+
+ PathDiagnosticLocation LocUsedForUniqueing;
+ const Stmt *CallSite = Context.getStackFrame()->getCallSite();
+ if (CallSite)
+ LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
+ CallSite, Context.getSourceManager(), Node->getLocationContext());
+
+ // For Plist consumers that don't support notes just yet, we'll convert notes
+ // to warnings.
+ if (Opts.ShouldConvertNotesToWarnings) {
+ for (const auto &Pair : UninitFields) {
+
+ auto Report = llvm::make_unique<BugReport>(
+ *BT_uninitField, Pair.second, Node, LocUsedForUniqueing,
+ Node->getLocationContext()->getDecl());
+ Context.emitReport(std::move(Report));
+ }
+ return;
+ }
+
+ SmallString<100> WarningBuf;
+ llvm::raw_svector_ostream WarningOS(WarningBuf);
+ WarningOS << UninitFields.size() << " uninitialized field"
+ << (UninitFields.size() == 1 ? "" : "s")
+ << " at the end of the constructor call";
+
+ auto Report = llvm::make_unique<BugReport>(
+ *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing,
+ Node->getLocationContext()->getDecl());
+
+ for (const auto &Pair : UninitFields) {
+ Report->addNote(Pair.second,
+ PathDiagnosticLocation::create(Pair.first->getDecl(),
+ Context.getSourceManager()));
+ }
+ Context.emitReport(std::move(Report));
+}
+
+void UninitializedObjectChecker::checkDeadSymbols(SymbolReaper &SR,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ for (const MemRegion *R : State->get<AnalyzedRegions>()) {
+ if (!SR.isLiveRegion(R))
+ State = State->remove<AnalyzedRegions>(R);
+ }
+}
+
+//===----------------------------------------------------------------------===//
+// Methods for FindUninitializedFields.
+//===----------------------------------------------------------------------===//
+
+FindUninitializedFields::FindUninitializedFields(
+ ProgramStateRef State, const TypedValueRegion *const R,
+ const UninitObjCheckerOptions &Opts)
+ : State(State), ObjectR(R), Opts(Opts) {
+
+ isNonUnionUninit(ObjectR, FieldChainInfo(ChainFactory));
+
+ // In non-pedantic mode, if ObjectR doesn't contain a single initialized
+ // field, we'll assume that Object was intentionally left uninitialized.
+ if (!Opts.IsPedantic && !isAnyFieldInitialized())
+ UninitFields.clear();
+}
+
+bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain,
+ const MemRegion *PointeeR) {
+ const FieldRegion *FR = Chain.getUninitRegion();
+
+ assert((PointeeR || !isDereferencableType(FR->getDecl()->getType())) &&
+ "One must also pass the pointee region as a parameter for "
+ "dereferenceable fields!");
+
+ if (State->contains<AnalyzedRegions>(FR))
+ return false;
+
+ if (PointeeR) {
+ if (State->contains<AnalyzedRegions>(PointeeR)) {
+ return false;
+ }
+ State = State->add<AnalyzedRegions>(PointeeR);
+ }
+
+ State = State->add<AnalyzedRegions>(FR);
+
+ if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
+ FR->getDecl()->getLocation()))
+ return false;
+
+ UninitFieldMap::mapped_type NoteMsgBuf;
+ llvm::raw_svector_ostream OS(NoteMsgBuf);
+ Chain.printNoteMsg(OS);
+ return UninitFields.insert({FR, std::move(NoteMsgBuf)}).second;
+}
+
+bool FindUninitializedFields::isNonUnionUninit(const TypedValueRegion *R,
+ FieldChainInfo LocalChain) {
+ assert(R->getValueType()->isRecordType() &&
+ !R->getValueType()->isUnionType() &&
+ "This method only checks non-union record objects!");
+
+ const RecordDecl *RD = R->getValueType()->getAsRecordDecl()->getDefinition();
+
+ if (!RD) {
+ IsAnyFieldInitialized = true;
+ return true;
+ }
+
+ if (!Opts.IgnoredRecordsWithFieldPattern.empty() &&
+ shouldIgnoreRecord(RD, Opts.IgnoredRecordsWithFieldPattern)) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ bool ContainsUninitField = false;
+
+ // Are all of this non-union's fields initialized?
+ for (const FieldDecl *I : RD->fields()) {
+
+ const auto FieldVal =
+ State->getLValue(I, loc::MemRegionVal(R)).castAs<loc::MemRegionVal>();
+ const auto *FR = FieldVal.getRegionAs<FieldRegion>();
+ QualType T = I->getType();
+
+ // If LocalChain already contains FR, then we encountered a cyclic
+ // reference. In this case, region FR is already under checking at an
+ // earlier node in the directed tree.
+ if (LocalChain.contains(FR))
+ return false;
+
+ if (T->isStructureOrClassType()) {
+ if (isNonUnionUninit(FR, LocalChain.add(RegularField(FR))))
+ ContainsUninitField = true;
+ continue;
+ }
+
+ if (T->isUnionType()) {
+ if (isUnionUninit(FR)) {
+ if (addFieldToUninits(LocalChain.add(RegularField(FR))))
+ ContainsUninitField = true;
+ } else
+ IsAnyFieldInitialized = true;
+ continue;
+ }
+
+ if (T->isArrayType()) {
+ IsAnyFieldInitialized = true;
+ continue;
+ }
+
+ SVal V = State->getSVal(FieldVal);
+
+ if (isDereferencableType(T) || V.getAs<nonloc::LocAsInteger>()) {
+ if (isDereferencableUninit(FR, LocalChain))
+ ContainsUninitField = true;
+ continue;
+ }
+
+ if (isPrimitiveType(T)) {
+ if (isPrimitiveUninit(V)) {
+ if (addFieldToUninits(LocalChain.add(RegularField(FR))))
+ ContainsUninitField = true;
+ }
+ continue;
+ }
+
+ llvm_unreachable("All cases are handled!");
+ }
+
+ // Checking bases. The checker will regard inherited data members as direct
+ // fields.
+ const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD);
+ if (!CXXRD)
+ return ContainsUninitField;
+
+ for (const CXXBaseSpecifier &BaseSpec : CXXRD->bases()) {
+ const auto *BaseRegion = State->getLValue(BaseSpec, R)
+ .castAs<loc::MemRegionVal>()
+ .getRegionAs<TypedValueRegion>();
+
+ // If the head of the list is also a BaseClass, we'll overwrite it to avoid
+ // note messages like 'this->A::B::x'.
+ if (!LocalChain.isEmpty() && LocalChain.getHead().isBase()) {
+ if (isNonUnionUninit(BaseRegion, LocalChain.replaceHead(
+ BaseClass(BaseSpec.getType()))))
+ ContainsUninitField = true;
+ } else {
+ if (isNonUnionUninit(BaseRegion,
+ LocalChain.add(BaseClass(BaseSpec.getType()))))
+ ContainsUninitField = true;
+ }
+ }
+
+ return ContainsUninitField;
+}
+
+bool FindUninitializedFields::isUnionUninit(const TypedValueRegion *R) {
+ assert(R->getValueType()->isUnionType() &&
+ "This method only checks union objects!");
+ // TODO: Implement support for union fields.
+ return false;
+}
+
+bool FindUninitializedFields::isPrimitiveUninit(const SVal &V) {
+ if (V.isUndef())
+ return true;
+
+ IsAnyFieldInitialized = true;
+ return false;
+}
+
+//===----------------------------------------------------------------------===//
+// Methods for FieldChainInfo.
+//===----------------------------------------------------------------------===//
+
+bool FieldChainInfo::contains(const FieldRegion *FR) const {
+ for (const FieldNode &Node : Chain) {
+ if (Node.isSameRegion(FR))
+ return true;
+ }
+ return false;
+}
+
+/// Prints every element except the last to `Out`. Since ImmutableLists store
+/// elements in reverse order, and have no reverse iterators, we use a
+/// recursive function to print the fieldchain correctly. The last element in
+/// the chain is to be printed by `FieldChainInfo::print`.
+static void printTail(llvm::raw_ostream &Out,
+ const FieldChainInfo::FieldChain L);
+
+// FIXME: This function constructs an incorrect string in the following case:
+//
+// struct Base { int x; };
+// struct D1 : Base {}; struct D2 : Base {};
+//
+// struct MostDerived : D1, D2 {
+// MostDerived() {}
+// }
+//
+// A call to MostDerived::MostDerived() will cause two notes that say
+// "uninitialized field 'this->x'", but we can't refer to 'x' directly,
+// we need an explicit namespace resolution whether the uninit field was
+// 'D1::x' or 'D2::x'.
+void FieldChainInfo::printNoteMsg(llvm::raw_ostream &Out) const {
+ if (Chain.isEmpty())
+ return;
+
+ const FieldNode &LastField = getHead();
+
+ LastField.printNoteMsg(Out);
+ Out << '\'';
+
+ for (const FieldNode &Node : Chain)
+ Node.printPrefix(Out);
+
+ Out << "this->";
+ printTail(Out, Chain.getTail());
+ LastField.printNode(Out);
+ Out << '\'';
+}
+
+static void printTail(llvm::raw_ostream &Out,
+ const FieldChainInfo::FieldChain L) {
+ if (L.isEmpty())
+ return;
+
+ printTail(Out, L.getTail());
+
+ L.getHead().printNode(Out);
+ L.getHead().printSeparator(Out);
+}
+
+//===----------------------------------------------------------------------===//
+// Utility functions.
+//===----------------------------------------------------------------------===//
+
+static const TypedValueRegion *
+getConstructedRegion(const CXXConstructorDecl *CtorDecl,
+ CheckerContext &Context) {
+
+ Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl,
+ Context.getStackFrame());
+
+ SVal ObjectV = Context.getState()->getSVal(ThisLoc);
+
+ auto *R = ObjectV.getAsRegion()->getAs<TypedValueRegion>();
+ if (R && !R->getValueType()->getAsCXXRecordDecl())
+ return nullptr;
+
+ return R;
+}
+
+static bool willObjectBeAnalyzedLater(const CXXConstructorDecl *Ctor,
+ CheckerContext &Context) {
+
+ const TypedValueRegion *CurrRegion = getConstructedRegion(Ctor, Context);
+ if (!CurrRegion)
+ return false;
+
+ const LocationContext *LC = Context.getLocationContext();
+ while ((LC = LC->getParent())) {
+
+ // If \p Ctor was called by another constructor.
+ const auto *OtherCtor = dyn_cast<CXXConstructorDecl>(LC->getDecl());
+ if (!OtherCtor)
+ continue;
+
+ const TypedValueRegion *OtherRegion =
+ getConstructedRegion(OtherCtor, Context);
+ if (!OtherRegion)
+ continue;
+
+ // If the CurrRegion is a subregion of OtherRegion, it will be analyzed
+ // during the analysis of OtherRegion.
+ if (CurrRegion->isSubRegionOf(OtherRegion))
+ return true;
+ }
+
+ return false;
+}
+
+static bool shouldIgnoreRecord(const RecordDecl *RD, StringRef Pattern) {
+ llvm::Regex R(Pattern);
+
+ for (const FieldDecl *FD : RD->fields()) {
+ if (R.match(FD->getType().getAsString()))
+ return true;
+ if (R.match(FD->getName()))
+ return true;
+ }
+
+ return false;
+}
+
+std::string clang::ento::getVariableName(const FieldDecl *Field) {
+ // If Field is a captured lambda variable, Field->getName() will return with
+ // an empty string. We can however acquire it's name from the lambda's
+ // captures.
+ const auto *CXXParent = dyn_cast<CXXRecordDecl>(Field->getParent());
+
+ if (CXXParent && CXXParent->isLambda()) {
+ assert(CXXParent->captures_begin());
+ auto It = CXXParent->captures_begin() + Field->getFieldIndex();
+
+ if (It->capturesVariable())
+ return llvm::Twine("/*captured variable*/" +
+ It->getCapturedVar()->getName())
+ .str();
+
+ if (It->capturesThis())
+ return "/*'this' capture*/";
+
+ llvm_unreachable("No other capture type is expected!");
+ }
+
+ return Field->getName();
+}
+
+void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) {
+ auto Chk = Mgr.registerChecker<UninitializedObjectChecker>();
+
+ AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions();
+ UninitObjCheckerOptions &ChOpts = Chk->Opts;
+
+ ChOpts.IsPedantic =
+ AnOpts.getCheckerBooleanOption("Pedantic", /*DefaultVal*/ false, Chk);
+ ChOpts.ShouldConvertNotesToWarnings =
+ AnOpts.getCheckerBooleanOption("NotesAsWarnings", /*DefaultVal*/ false, Chk);
+ ChOpts.CheckPointeeInitialization = AnOpts.getCheckerBooleanOption(
+ "CheckPointeeInitialization", /*DefaultVal*/ false, Chk);
+ ChOpts.IgnoredRecordsWithFieldPattern =
+ AnOpts.getCheckerStringOption("IgnoreRecordsWithField",
+ /*DefaultVal*/ "", Chk);
+}
diff --git a/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp
new file mode 100644
index 000000000000..aead59c7bf87
--- /dev/null
+++ b/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp
@@ -0,0 +1,282 @@
+//===----- UninitializedPointee.cpp ------------------------------*- 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 functions and methods for handling pointers and references
+// to reduce the size and complexity of UninitializedObjectChecker.cpp.
+//
+// To read about command line options and documentation about how the checker
+// works, refer to UninitializedObjectChecker.h.
+//
+//===----------------------------------------------------------------------===//
+
+#include "UninitializedObject.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h"
+
+using namespace clang;
+using namespace clang::ento;
+
+namespace {
+
+/// Represents a pointer or a reference field.
+class LocField final : public FieldNode {
+ /// We'll store whether the pointee or the pointer itself is uninitialited.
+ const bool IsDereferenced;
+
+public:
+ LocField(const FieldRegion *FR, const bool IsDereferenced = true)
+ : FieldNode(FR), IsDereferenced(IsDereferenced) {}
+
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const override {
+ if (IsDereferenced)
+ Out << "uninitialized pointee ";
+ else
+ Out << "uninitialized pointer ";
+ }
+
+ virtual void printPrefix(llvm::raw_ostream &Out) const override {}
+
+ virtual void printNode(llvm::raw_ostream &Out) const override {
+ Out << getVariableName(getDecl());
+ }
+
+ virtual void printSeparator(llvm::raw_ostream &Out) const override {
+ if (getDecl()->getType()->isPointerType())
+ Out << "->";
+ else
+ Out << '.';
+ }
+};
+
+/// Represents a nonloc::LocAsInteger or void* field, that point to objects, but
+/// needs to be casted back to its dynamic type for a correct note message.
+class NeedsCastLocField final : public FieldNode {
+ QualType CastBackType;
+
+public:
+ NeedsCastLocField(const FieldRegion *FR, const QualType &T)
+ : FieldNode(FR), CastBackType(T) {}
+
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const override {
+ Out << "uninitialized pointee ";
+ }
+
+ virtual void printPrefix(llvm::raw_ostream &Out) const override {
+ // If this object is a nonloc::LocAsInteger.
+ if (getDecl()->getType()->isIntegerType())
+ Out << "reinterpret_cast";
+ // If this pointer's dynamic type is different then it's static type.
+ else
+ Out << "static_cast";
+ Out << '<' << CastBackType.getAsString() << ">(";
+ }
+
+ virtual void printNode(llvm::raw_ostream &Out) const override {
+ Out << getVariableName(getDecl()) << ')';
+ }
+
+ virtual void printSeparator(llvm::raw_ostream &Out) const override {
+ Out << "->";
+ }
+};
+
+/// Represents a Loc field that points to itself.
+class CyclicLocField final : public FieldNode {
+
+public:
+ CyclicLocField(const FieldRegion *FR) : FieldNode(FR) {}
+
+ virtual void printNoteMsg(llvm::raw_ostream &Out) const override {
+ Out << "object references itself ";
+ }
+
+ virtual void printPrefix(llvm::raw_ostream &Out) const override {}
+
+ virtual void printNode(llvm::raw_ostream &Out) const override {
+ Out << getVariableName(getDecl());
+ }
+
+ virtual void printSeparator(llvm::raw_ostream &Out) const override {
+ llvm_unreachable("CyclicLocField objects must be the last node of the "
+ "fieldchain!");
+ }
+};
+
+} // end of anonymous namespace
+
+// Utility function declarations.
+
+struct DereferenceInfo {
+ const TypedValueRegion *R;
+ const bool NeedsCastBack;
+ const bool IsCyclic;
+ DereferenceInfo(const TypedValueRegion *R, bool NCB, bool IC)
+ : R(R), NeedsCastBack(NCB), IsCyclic(IC) {}
+};
+
+/// Dereferences \p FR and returns with the pointee's region, and whether it
+/// needs to be casted back to it's location type. If for whatever reason
+/// dereferencing fails, returns with None.
+static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State,
+ const FieldRegion *FR);
+
+/// Returns whether \p T can be (transitively) dereferenced to a void pointer
+/// type (void*, void**, ...).
+static bool isVoidPointer(QualType T);
+
+//===----------------------------------------------------------------------===//
+// Methods for FindUninitializedFields.
+//===----------------------------------------------------------------------===//
+
+bool FindUninitializedFields::isDereferencableUninit(
+ const FieldRegion *FR, FieldChainInfo LocalChain) {
+
+ SVal V = State->getSVal(FR);
+
+ assert((isDereferencableType(FR->getDecl()->getType()) ||
+ V.getAs<nonloc::LocAsInteger>()) &&
+ "This method only checks dereferenceable objects!");
+
+ if (V.isUnknown() || V.getAs<loc::ConcreteInt>()) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ if (V.isUndef()) {
+ return addFieldToUninits(
+ LocalChain.add(LocField(FR, /*IsDereferenced*/ false)), FR);
+ }
+
+ if (!Opts.CheckPointeeInitialization) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ // At this point the pointer itself is initialized and points to a valid
+ // location, we'll now check the pointee.
+ llvm::Optional<DereferenceInfo> DerefInfo = dereference(State, FR);
+ if (!DerefInfo) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ if (DerefInfo->IsCyclic)
+ return addFieldToUninits(LocalChain.add(CyclicLocField(FR)), FR);
+
+ const TypedValueRegion *R = DerefInfo->R;
+ const bool NeedsCastBack = DerefInfo->NeedsCastBack;
+
+ QualType DynT = R->getLocationType();
+ QualType PointeeT = DynT->getPointeeType();
+
+ if (PointeeT->isStructureOrClassType()) {
+ if (NeedsCastBack)
+ return isNonUnionUninit(R, LocalChain.add(NeedsCastLocField(FR, DynT)));
+ return isNonUnionUninit(R, LocalChain.add(LocField(FR)));
+ }
+
+ if (PointeeT->isUnionType()) {
+ if (isUnionUninit(R)) {
+ if (NeedsCastBack)
+ return addFieldToUninits(LocalChain.add(NeedsCastLocField(FR, DynT)),
+ R);
+ return addFieldToUninits(LocalChain.add(LocField(FR)), R);
+ } else {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+ }
+
+ if (PointeeT->isArrayType()) {
+ IsAnyFieldInitialized = true;
+ return false;
+ }
+
+ assert((isPrimitiveType(PointeeT) || isDereferencableType(PointeeT)) &&
+ "At this point FR must either have a primitive dynamic type, or it "
+ "must be a null, undefined, unknown or concrete pointer!");
+
+ SVal PointeeV = State->getSVal(R);
+
+ if (isPrimitiveUninit(PointeeV)) {
+ if (NeedsCastBack)
+ return addFieldToUninits(LocalChain.add(NeedsCastLocField(FR, DynT)), R);
+ return addFieldToUninits(LocalChain.add(LocField(FR)), R);
+ }
+
+ IsAnyFieldInitialized = true;
+ return false;
+}
+
+//===----------------------------------------------------------------------===//
+// Utility functions.
+//===----------------------------------------------------------------------===//
+
+static llvm::Optional<DereferenceInfo> dereference(ProgramStateRef State,
+ const FieldRegion *FR) {
+
+ llvm::SmallSet<const TypedValueRegion *, 5> VisitedRegions;
+
+ SVal V = State->getSVal(FR);
+ assert(V.getAsRegion() && "V must have an underlying region!");
+
+ // If the static type of the field is a void pointer, or it is a
+ // nonloc::LocAsInteger, we need to cast it back to the dynamic type before
+ // dereferencing.
+ bool NeedsCastBack = isVoidPointer(FR->getDecl()->getType()) ||
+ V.getAs<nonloc::LocAsInteger>();
+
+ // The region we'd like to acquire.
+ const auto *R = V.getAsRegion()->getAs<TypedValueRegion>();
+ if (!R)
+ return None;
+
+ VisitedRegions.insert(R);
+
+ // We acquire the dynamic type of R,
+ QualType DynT = R->getLocationType();
+
+ while (const MemRegion *Tmp = State->getSVal(R, DynT).getAsRegion()) {
+
+ R = Tmp->getAs<TypedValueRegion>();
+ if (!R)
+ return None;
+
+ // We found a cyclic pointer, like int *ptr = (int *)&ptr.
+ if (!VisitedRegions.insert(R).second)
+ return DereferenceInfo{R, NeedsCastBack, /*IsCyclic*/ true};
+
+ DynT = R->getLocationType();
+ // In order to ensure that this loop terminates, we're also checking the
+ // dynamic type of R, since type hierarchy is finite.
+ if (isDereferencableType(DynT->getPointeeType()))
+ break;
+ }
+
+ while (R->getAs<CXXBaseObjectRegion>()) {
+ NeedsCastBack = true;
+
+ if (!isa<TypedValueRegion>(R->getSuperRegion()))
+ break;
+ R = R->getSuperRegion()->getAs<TypedValueRegion>();
+ }
+
+ return DereferenceInfo{R, NeedsCastBack, /*IsCyclic*/ false};
+}
+
+static bool isVoidPointer(QualType T) {
+ while (!T.isNull()) {
+ if (T->isVoidPointerType())
+ return true;
+ T = T->getPointeeType();
+ }
+ return false;
+}
diff --git a/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp
deleted file mode 100644
index 398228a9d887..000000000000
--- a/lib/StaticAnalyzer/Checkers/UninitializedObjectChecker.cpp
+++ /dev/null
@@ -1,688 +0,0 @@
-//===----- UninitializedObjectChecker.cpp ------------------------*- 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 a checker that reports uninitialized fields in objects
-// created after a constructor call.
-//
-// This checker has two options:
-// - "Pedantic" (boolean). If its not set or is set to false, the checker
-// won't emit warnings for objects that don't have at least one initialized
-// field. This may be set with
-//
-// `-analyzer-config alpha.cplusplus.UninitializedObject:Pedantic=true`.
-//
-// - "NotesAsWarnings" (boolean). If set to true, the checker will emit a
-// warning for each uninitalized field, as opposed to emitting one warning
-// per constructor call, and listing the uninitialized fields that belongs
-// to it in notes. Defaults to false.
-//
-// `-analyzer-config alpha.cplusplus.UninitializedObject:NotesAsWarnings=true`.
-//
-//===----------------------------------------------------------------------===//
-
-#include "ClangSACheckers.h"
-#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
-#include "clang/StaticAnalyzer/Core/Checker.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
-#include <algorithm>
-
-using namespace clang;
-using namespace clang::ento;
-
-namespace {
-
-class UninitializedObjectChecker : public Checker<check::EndFunction> {
- std::unique_ptr<BuiltinBug> BT_uninitField;
-
-public:
- // These fields will be initialized when registering the checker.
- bool IsPedantic;
- bool ShouldConvertNotesToWarnings;
-
- UninitializedObjectChecker()
- : BT_uninitField(new BuiltinBug(this, "Uninitialized fields")) {}
- void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const;
-};
-
-/// Represents a field chain. A field chain is a vector of fields where the
-/// first element of the chain is the object under checking (not stored), and
-/// every other element is a field, and the element that precedes it is the
-/// object that contains it.
-///
-/// Note that this class is immutable, and new fields may only be added through
-/// constructor calls.
-class FieldChainInfo {
- using FieldChain = llvm::ImmutableList<const FieldRegion *>;
-
- FieldChain Chain;
-
- const bool IsDereferenced = false;
-
-public:
- FieldChainInfo() = default;
-
- FieldChainInfo(const FieldChainInfo &Other, const bool IsDereferenced)
- : Chain(Other.Chain), IsDereferenced(IsDereferenced) {}
-
- FieldChainInfo(const FieldChainInfo &Other, const FieldRegion *FR,
- const bool IsDereferenced = false);
-
- bool contains(const FieldRegion *FR) const { return Chain.contains(FR); }
- bool isPointer() const;
-
- /// If this is a fieldchain whose last element is an uninitialized region of a
- /// pointer type, `IsDereferenced` will store whether the pointer itself or
- /// the pointee is uninitialized.
- bool isDereferenced() const;
- const FieldDecl *getEndOfChain() const;
- void print(llvm::raw_ostream &Out) const;
-
-private:
- /// Prints every element except the last to `Out`. Since ImmutableLists store
- /// elements in reverse order, and have no reverse iterators, we use a
- /// recursive function to print the fieldchain correctly. The last element in
- /// the chain is to be printed by `print`.
- static void printTail(llvm::raw_ostream &Out,
- const llvm::ImmutableListImpl<const FieldRegion *> *L);
- friend struct FieldChainInfoComparator;
-};
-
-struct FieldChainInfoComparator {
- bool operator()(const FieldChainInfo &lhs, const FieldChainInfo &rhs) const {
- assert(!lhs.Chain.isEmpty() && !rhs.Chain.isEmpty() &&
- "Attempted to store an empty fieldchain!");
- return *lhs.Chain.begin() < *rhs.Chain.begin();
- }
-};
-
-using UninitFieldSet = std::set<FieldChainInfo, FieldChainInfoComparator>;
-
-/// Searches for and stores uninitialized fields in a non-union object.
-class FindUninitializedFields {
- ProgramStateRef State;
- const TypedValueRegion *const ObjectR;
-
- const bool IsPedantic;
- bool IsAnyFieldInitialized = false;
-
- UninitFieldSet UninitFields;
-
-public:
- FindUninitializedFields(ProgramStateRef State,
- const TypedValueRegion *const R, bool IsPedantic);
- const UninitFieldSet &getUninitFields();
-
-private:
- /// Adds a FieldChainInfo object to UninitFields. Return true if an insertion
- /// took place.
- bool addFieldToUninits(FieldChainInfo LocalChain);
-
- // For the purposes of this checker, we'll regard the object under checking as
- // a directed tree, where
- // * the root is the object under checking
- // * every node is an object that is
- // - a union
- // - a non-union record
- // - a pointer/reference
- // - an array
- // - of a primitive type, which we'll define later in a helper function.
- // * the parent of each node is the object that contains it
- // * every leaf is an array, a primitive object, a nullptr or an undefined
- // pointer.
- //
- // Example:
- //
- // struct A {
- // struct B {
- // int x, y = 0;
- // };
- // B b;
- // int *iptr = new int;
- // B* bptr;
- //
- // A() {}
- // };
- //
- // The directed tree:
- //
- // ->x
- // /
- // ->b--->y
- // /
- // A-->iptr->(int value)
- // \
- // ->bptr
- //
- // From this we'll construct a vector of fieldchains, where each fieldchain
- // represents an uninitialized field. An uninitialized field may be a
- // primitive object, a pointer, a pointee or a union without a single
- // initialized field.
- // In the above example, for the default constructor call we'll end up with
- // these fieldchains:
- //
- // this->b.x
- // this->iptr (pointee uninit)
- // this->bptr (pointer uninit)
- //
- // We'll traverse each node of the above graph with the appropiate one of
- // these methods:
-
- /// This method checks a region of a union object, and returns true if no
- /// field is initialized within the region.
- bool isUnionUninit(const TypedValueRegion *R);
-
- /// This method checks a region of a non-union object, and returns true if
- /// an uninitialized field is found within the region.
- bool isNonUnionUninit(const TypedValueRegion *R, FieldChainInfo LocalChain);
-
- /// This method checks a region of a pointer or reference object, and returns
- /// true if the ptr/ref object itself or any field within the pointee's region
- /// is uninitialized.
- bool isPointerOrReferenceUninit(const FieldRegion *FR,
- FieldChainInfo LocalChain);
-
- /// This method returns true if the value of a primitive object is
- /// uninitialized.
- bool isPrimitiveUninit(const SVal &V);
-
- // Note that we don't have a method for arrays -- the elements of an array are
- // often left uninitialized intentionally even when it is of a C++ record
- // type, so we'll assume that an array is always initialized.
- // TODO: Add a support for nonloc::LocAsInteger.
-};
-
-} // end of anonymous namespace
-
-// Static variable instantionations.
-
-static llvm::ImmutableListFactory<const FieldRegion *> Factory;
-
-// Utility function declarations.
-
-/// Returns the object that was constructed by CtorDecl, or None if that isn't
-/// possible.
-static Optional<nonloc::LazyCompoundVal>
-getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context);
-
-/// Checks whether the constructor under checking is called by another
-/// constructor.
-static bool isCalledByConstructor(const CheckerContext &Context);
-
-/// Returns whether FD can be (transitively) dereferenced to a void pointer type
-/// (void*, void**, ...). The type of the region behind a void pointer isn't
-/// known, and thus FD can not be analyzed.
-static bool isVoidPointer(const FieldDecl *FD);
-
-/// Returns true if T is a primitive type. We defined this type so that for
-/// objects that we'd only like analyze as much as checking whether their
-/// value is undefined or not, such as ints and doubles, can be analyzed with
-/// ease. This also helps ensuring that every special field type is handled
-/// correctly.
-static bool isPrimitiveType(const QualType &T) {
- return T->isBuiltinType() || T->isEnumeralType() || T->isMemberPointerType();
-}
-
-/// Constructs a note message for a given FieldChainInfo object.
-static void printNoteMessage(llvm::raw_ostream &Out,
- const FieldChainInfo &Chain);
-
-/// Returns with Field's name. This is a helper function to get the correct name
-/// even if Field is a captured lambda variable.
-static StringRef getVariableName(const FieldDecl *Field);
-
-//===----------------------------------------------------------------------===//
-// Methods for UninitializedObjectChecker.
-//===----------------------------------------------------------------------===//
-
-void UninitializedObjectChecker::checkEndFunction(
- const ReturnStmt *RS, CheckerContext &Context) const {
-
- const auto *CtorDecl = dyn_cast_or_null<CXXConstructorDecl>(
- Context.getLocationContext()->getDecl());
- if (!CtorDecl)
- return;
-
- if (!CtorDecl->isUserProvided())
- return;
-
- if (CtorDecl->getParent()->isUnion())
- return;
-
- // This avoids essentially the same error being reported multiple times.
- if (isCalledByConstructor(Context))
- return;
-
- Optional<nonloc::LazyCompoundVal> Object = getObjectVal(CtorDecl, Context);
- if (!Object)
- return;
-
- FindUninitializedFields F(Context.getState(), Object->getRegion(),
- IsPedantic);
-
- const UninitFieldSet &UninitFields = F.getUninitFields();
-
- if (UninitFields.empty())
- return;
-
- // There are uninitialized fields in the record.
-
- ExplodedNode *Node = Context.generateNonFatalErrorNode(Context.getState());
- if (!Node)
- return;
-
- PathDiagnosticLocation LocUsedForUniqueing;
- const Stmt *CallSite = Context.getStackFrame()->getCallSite();
- if (CallSite)
- LocUsedForUniqueing = PathDiagnosticLocation::createBegin(
- CallSite, Context.getSourceManager(), Node->getLocationContext());
-
- // For Plist consumers that don't support notes just yet, we'll convert notes
- // to warnings.
- if (ShouldConvertNotesToWarnings) {
- for (const auto &Chain : UninitFields) {
- SmallString<100> WarningBuf;
- llvm::raw_svector_ostream WarningOS(WarningBuf);
-
- printNoteMessage(WarningOS, Chain);
-
- auto Report = llvm::make_unique<BugReport>(
- *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing,
- Node->getLocationContext()->getDecl());
- Context.emitReport(std::move(Report));
- }
- return;
- }
-
- SmallString<100> WarningBuf;
- llvm::raw_svector_ostream WarningOS(WarningBuf);
- WarningOS << UninitFields.size() << " uninitialized field"
- << (UninitFields.size() == 1 ? "" : "s")
- << " at the end of the constructor call";
-
- auto Report = llvm::make_unique<BugReport>(
- *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing,
- Node->getLocationContext()->getDecl());
-
- for (const auto &Chain : UninitFields) {
- SmallString<200> NoteBuf;
- llvm::raw_svector_ostream NoteOS(NoteBuf);
-
- printNoteMessage(NoteOS, Chain);
-
- Report->addNote(NoteOS.str(),
- PathDiagnosticLocation::create(Chain.getEndOfChain(),
- Context.getSourceManager()));
- }
- Context.emitReport(std::move(Report));
-}
-
-//===----------------------------------------------------------------------===//
-// Methods for FindUninitializedFields.
-//===----------------------------------------------------------------------===//
-
-FindUninitializedFields::FindUninitializedFields(
- ProgramStateRef State, const TypedValueRegion *const R, bool IsPedantic)
- : State(State), ObjectR(R), IsPedantic(IsPedantic) {}
-
-const UninitFieldSet &FindUninitializedFields::getUninitFields() {
- isNonUnionUninit(ObjectR, FieldChainInfo());
-
- if (!IsPedantic && !IsAnyFieldInitialized)
- UninitFields.clear();
-
- return UninitFields;
-}
-
-bool FindUninitializedFields::addFieldToUninits(FieldChainInfo Chain) {
- if (State->getStateManager().getContext().getSourceManager().isInSystemHeader(
- Chain.getEndOfChain()->getLocation()))
- return false;
-
- return UninitFields.insert(Chain).second;
-}
-
-bool FindUninitializedFields::isNonUnionUninit(const TypedValueRegion *R,
- FieldChainInfo LocalChain) {
- assert(R->getValueType()->isRecordType() &&
- !R->getValueType()->isUnionType() &&
- "This method only checks non-union record objects!");
-
- const RecordDecl *RD =
- R->getValueType()->getAs<RecordType>()->getDecl()->getDefinition();
- assert(RD && "Referred record has no definition");
-
- bool ContainsUninitField = false;
-
- // Are all of this non-union's fields initialized?
- for (const FieldDecl *I : RD->fields()) {
-
- const auto FieldVal =
- State->getLValue(I, loc::MemRegionVal(R)).castAs<loc::MemRegionVal>();
- const auto *FR = FieldVal.getRegionAs<FieldRegion>();
- QualType T = I->getType();
-
- // If LocalChain already contains FR, then we encountered a cyclic
- // reference. In this case, region FR is already under checking at an
- // earlier node in the directed tree.
- if (LocalChain.contains(FR))
- return false;
-
- if (T->isStructureOrClassType()) {
- if (isNonUnionUninit(FR, {LocalChain, FR}))
- ContainsUninitField = true;
- continue;
- }
-
- if (T->isUnionType()) {
- if (isUnionUninit(FR)) {
- if (addFieldToUninits({LocalChain, FR}))
- ContainsUninitField = true;
- } else
- IsAnyFieldInitialized = true;
- continue;
- }
-
- if (T->isArrayType()) {
- IsAnyFieldInitialized = true;
- continue;
- }
-
- if (T->isPointerType() || T->isReferenceType()) {
- if (isPointerOrReferenceUninit(FR, LocalChain))
- ContainsUninitField = true;
- continue;
- }
-
- if (isPrimitiveType(T)) {
- SVal V = State->getSVal(FieldVal);
-
- if (isPrimitiveUninit(V)) {
- if (addFieldToUninits({LocalChain, FR}))
- ContainsUninitField = true;
- }
- continue;
- }
-
- llvm_unreachable("All cases are handled!");
- }
-
- // Checking bases.
- // FIXME: As of now, because of `isCalledByConstructor`, objects whose type
- // is a descendant of another type will emit warnings for uninitalized
- // inherited members.
- // This is not the only way to analyze bases of an object -- if we didn't
- // filter them out, and didn't analyze the bases, this checker would run for
- // each base of the object in order of base initailization and in theory would
- // find every uninitalized field. This approach could also make handling
- // diamond inheritances more easily.
- //
- // This rule (that a descendant type's cunstructor is responsible for
- // initializing inherited data members) is not obvious, and should it should
- // be.
- const auto *CXXRD = dyn_cast<CXXRecordDecl>(RD);
- if (!CXXRD)
- return ContainsUninitField;
-
- for (const CXXBaseSpecifier &BaseSpec : CXXRD->bases()) {
- const auto *BaseRegion = State->getLValue(BaseSpec, R)
- .castAs<loc::MemRegionVal>()
- .getRegionAs<TypedValueRegion>();
-
- if (isNonUnionUninit(BaseRegion, LocalChain))
- ContainsUninitField = true;
- }
-
- return ContainsUninitField;
-}
-
-bool FindUninitializedFields::isUnionUninit(const TypedValueRegion *R) {
- assert(R->getValueType()->isUnionType() &&
- "This method only checks union objects!");
- // TODO: Implement support for union fields.
- return false;
-}
-
-// Note that pointers/references don't contain fields themselves, so in this
-// function we won't add anything to LocalChain.
-bool FindUninitializedFields::isPointerOrReferenceUninit(
- const FieldRegion *FR, FieldChainInfo LocalChain) {
-
- assert((FR->getDecl()->getType()->isPointerType() ||
- FR->getDecl()->getType()->isReferenceType()) &&
- "This method only checks pointer/reference objects!");
-
- SVal V = State->getSVal(FR);
-
- if (V.isUnknown() || V.isZeroConstant()) {
- IsAnyFieldInitialized = true;
- return false;
- }
-
- if (V.isUndef()) {
- return addFieldToUninits({LocalChain, FR});
- }
-
- const FieldDecl *FD = FR->getDecl();
-
- // TODO: The dynamic type of a void pointer may be retrieved with
- // `getDynamicTypeInfo`.
- if (isVoidPointer(FD)) {
- IsAnyFieldInitialized = true;
- return false;
- }
-
- assert(V.getAs<Loc>() && "V should be Loc at this point!");
-
- // At this point the pointer itself is initialized and points to a valid
- // location, we'll now check the pointee.
- SVal DerefdV = State->getSVal(V.castAs<Loc>());
-
- // TODO: Dereferencing should be done according to the dynamic type.
- while (Optional<Loc> L = DerefdV.getAs<Loc>()) {
- DerefdV = State->getSVal(*L);
- }
-
- // If V is a pointer pointing to a record type.
- if (Optional<nonloc::LazyCompoundVal> RecordV =
- DerefdV.getAs<nonloc::LazyCompoundVal>()) {
-
- const TypedValueRegion *R = RecordV->getRegion();
-
- // We can't reason about symbolic regions, assume its initialized.
- // Note that this also avoids a potential infinite recursion, because
- // constructors for list-like classes are checked without being called, and
- // the Static Analyzer will construct a symbolic region for Node *next; or
- // similar code snippets.
- if (R->getSymbolicBase()) {
- IsAnyFieldInitialized = true;
- return false;
- }
-
- const QualType T = R->getValueType();
-
- if (T->isStructureOrClassType())
- return isNonUnionUninit(R, {LocalChain, FR});
-
- if (T->isUnionType()) {
- if (isUnionUninit(R)) {
- return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true});
- } else {
- IsAnyFieldInitialized = true;
- return false;
- }
- }
-
- if (T->isArrayType()) {
- IsAnyFieldInitialized = true;
- return false;
- }
-
- llvm_unreachable("All cases are handled!");
- }
-
- // TODO: If possible, it should be asserted that the DerefdV at this point is
- // primitive.
-
- if (isPrimitiveUninit(DerefdV))
- return addFieldToUninits({LocalChain, FR, /*IsDereferenced*/ true});
-
- IsAnyFieldInitialized = true;
- return false;
-}
-
-bool FindUninitializedFields::isPrimitiveUninit(const SVal &V) {
- if (V.isUndef())
- return true;
-
- IsAnyFieldInitialized = true;
- return false;
-}
-
-//===----------------------------------------------------------------------===//
-// Methods for FieldChainInfo.
-//===----------------------------------------------------------------------===//
-
-FieldChainInfo::FieldChainInfo(const FieldChainInfo &Other,
- const FieldRegion *FR, const bool IsDereferenced)
- : FieldChainInfo(Other, IsDereferenced) {
- assert(!contains(FR) && "Can't add a field that is already a part of the "
- "fieldchain! Is this a cyclic reference?");
- Chain = Factory.add(FR, Other.Chain);
-}
-
-bool FieldChainInfo::isPointer() const {
- assert(!Chain.isEmpty() && "Empty fieldchain!");
- return (*Chain.begin())->getDecl()->getType()->isPointerType();
-}
-
-bool FieldChainInfo::isDereferenced() const {
- assert(isPointer() && "Only pointers may or may not be dereferenced!");
- return IsDereferenced;
-}
-
-const FieldDecl *FieldChainInfo::getEndOfChain() const {
- assert(!Chain.isEmpty() && "Empty fieldchain!");
- return (*Chain.begin())->getDecl();
-}
-
-// TODO: This function constructs an incorrect fieldchain string in the
-// following case:
-//
-// struct Base { int x; };
-// struct D1 : Base {}; struct D2 : Base {};
-//
-// struct MostDerived : D1, D2 {
-// MostDerived() {}
-// }
-//
-// A call to MostDerived::MostDerived() will cause two notes that say
-// "uninitialized field 'this->x'", but we can't refer to 'x' directly,
-// we need an explicit namespace resolution whether the uninit field was
-// 'D1::x' or 'D2::x'.
-void FieldChainInfo::print(llvm::raw_ostream &Out) const {
- if (Chain.isEmpty())
- return;
-
- const llvm::ImmutableListImpl<const FieldRegion *> *L =
- Chain.getInternalPointer();
- printTail(Out, L->getTail());
- Out << getVariableName(L->getHead()->getDecl());
-}
-
-void FieldChainInfo::printTail(
- llvm::raw_ostream &Out,
- const llvm::ImmutableListImpl<const FieldRegion *> *L) {
- if (!L)
- return;
-
- printTail(Out, L->getTail());
- const FieldDecl *Field = L->getHead()->getDecl();
- Out << getVariableName(Field);
- Out << (Field->getType()->isPointerType() ? "->" : ".");
-}
-
-//===----------------------------------------------------------------------===//
-// Utility functions.
-//===----------------------------------------------------------------------===//
-
-static bool isVoidPointer(const FieldDecl *FD) {
- QualType T = FD->getType();
-
- while (!T.isNull()) {
- if (T->isVoidPointerType())
- return true;
- T = T->getPointeeType();
- }
- return false;
-}
-
-static Optional<nonloc::LazyCompoundVal>
-getObjectVal(const CXXConstructorDecl *CtorDecl, CheckerContext &Context) {
-
- Loc ThisLoc = Context.getSValBuilder().getCXXThis(CtorDecl->getParent(),
- Context.getStackFrame());
- // Getting the value for 'this'.
- SVal This = Context.getState()->getSVal(ThisLoc);
-
- // Getting the value for '*this'.
- SVal Object = Context.getState()->getSVal(This.castAs<Loc>());
-
- return Object.getAs<nonloc::LazyCompoundVal>();
-}
-
-// TODO: We should also check that if the constructor was called by another
-// constructor, whether those two are in any relation to one another. In it's
-// current state, this introduces some false negatives.
-static bool isCalledByConstructor(const CheckerContext &Context) {
- const LocationContext *LC = Context.getLocationContext()->getParent();
-
- while (LC) {
- if (isa<CXXConstructorDecl>(LC->getDecl()))
- return true;
-
- LC = LC->getParent();
- }
- return false;
-}
-
-static void printNoteMessage(llvm::raw_ostream &Out,
- const FieldChainInfo &Chain) {
- if (Chain.isPointer()) {
- if (Chain.isDereferenced())
- Out << "uninitialized pointee 'this->";
- else
- Out << "uninitialized pointer 'this->";
- } else
- Out << "uninitialized field 'this->";
- Chain.print(Out);
- Out << "'";
-}
-
-static StringRef getVariableName(const FieldDecl *Field) {
- // If Field is a captured lambda variable, Field->getName() will return with
- // an empty string. We can however acquire it's name from the lambda's
- // captures.
- const auto *CXXParent = dyn_cast<CXXRecordDecl>(Field->getParent());
-
- if (CXXParent && CXXParent->isLambda()) {
- assert(CXXParent->captures_begin());
- auto It = CXXParent->captures_begin() + Field->getFieldIndex();
- return It->getCapturedVar()->getName();
- }
-
- return Field->getName();
-}
-
-void ento::registerUninitializedObjectChecker(CheckerManager &Mgr) {
- auto Chk = Mgr.registerChecker<UninitializedObjectChecker>();
- Chk->IsPedantic = Mgr.getAnalyzerOptions().getBooleanOption(
- "Pedantic", /*DefaultVal*/ false, Chk);
- Chk->ShouldConvertNotesToWarnings = Mgr.getAnalyzerOptions().getBooleanOption(
- "NotesAsWarnings", /*DefaultVal*/ false, Chk);
-}
diff --git a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
index a6b50dc37740..bab0c12704fa 100644
--- a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -314,7 +314,7 @@ bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C,
auto report = llvm::make_unique<BugReport>(*BT_mallocZero, os.str(), N);
report->addRange(arg->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, arg, *report);
+ bugreporter::trackExpressionValue(N, arg, *report);
C.emitReport(std::move(report));
return true;
diff --git a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
index dbd12cc9b65a..16b4d5e925ba 100644
--- a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp
@@ -13,7 +13,7 @@
// A similar flow-sensitive only check exists in Analysis/ReachableCode.cpp
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/ParentMap.h"
#include "clang/Basic/Builtins.h"
#include "clang/Basic/SourceManager.h"
@@ -150,7 +150,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G,
if (const Stmt *S = getUnreachableStmt(CB)) {
// In macros, 'do {...} while (0)' is often used. Don't warn about the
// condition 0 when it is unreachable.
- if (S->getLocStart().isMacroID())
+ if (S->getBeginLoc().isMacroID())
if (const auto *I = dyn_cast<IntegerLiteral>(S))
if (I->getValue() == 0ULL)
if (const Stmt *Parent = PM->getParent(S))
@@ -232,7 +232,7 @@ bool UnreachableCodeChecker::isInvalidPath(const CFGBlock *CB,
if (!pred)
return false;
- // Get the predecessor block's terminator conditon
+ // Get the predecessor block's terminator condition
const Stmt *cond = pred->getTerminatorCondition();
//assert(cond && "CFGBlock's predecessor has a terminator condition");
diff --git a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
index 2584f2011819..e458e0554ee2 100644
--- a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp
@@ -14,7 +14,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/CharUnits.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
@@ -74,7 +74,7 @@ void VLASizeChecker::reportBug(
auto report = llvm::make_unique<BugReport>(*BT, os.str(), N);
report->addVisitor(std::move(Visitor));
report->addRange(SizeE->getSourceRange());
- bugreporter::trackNullOrUndefValue(N, SizeE, *report);
+ bugreporter::trackExpressionValue(N, SizeE, *report);
C.emitReport(std::move(report));
}
diff --git a/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/lib/StaticAnalyzer/Checkers/ValistChecker.cpp
index bd657340fcfb..748b226b7a1e 100644
--- a/lib/StaticAnalyzer/Checkers/ValistChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/ValistChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
@@ -90,7 +90,6 @@ private:
return std::make_shared<PathDiagnosticEventPiece>(L, BR.getDescription(), false);
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
@@ -376,10 +375,10 @@ void ValistChecker::checkVAListEndCall(const CallEvent &Call,
}
std::shared_ptr<PathDiagnosticPiece> ValistChecker::ValistBugVisitor::VisitNode(
- const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC,
+ const ExplodedNode *N, BugReporterContext &BRC,
BugReport &) {
ProgramStateRef State = N->getState();
- ProgramStateRef StatePrev = PrevN->getState();
+ ProgramStateRef StatePrev = N->getFirstPred()->getState();
const Stmt *S = PathDiagnosticLocation::getStmt(N);
if (!S)
diff --git a/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp
index 75aefc0e8384..3ee9f1a07fa2 100644
--- a/lib/StaticAnalyzer/Checkers/VforkChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp
@@ -25,7 +25,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
index 5b602468cdd4..567063197405 100644
--- a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
+++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp
@@ -12,7 +12,7 @@
//
//===----------------------------------------------------------------------===//
-#include "ClangSACheckers.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/AST/DeclCXX.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -72,7 +72,6 @@ private:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override;
};
@@ -84,9 +83,8 @@ REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState)
std::shared_ptr<PathDiagnosticPiece>
VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
- BugReport &BR) {
+ BugReport &) {
// We need the last ctor/dtor which call the virtual function.
// The visitor walks the ExplodedGraph backwards.
if (Found)
@@ -282,5 +280,6 @@ void ento::registerVirtualCallChecker(CheckerManager &mgr) {
VirtualCallChecker *checker = mgr.registerChecker<VirtualCallChecker>();
checker->IsPureOnly =
- mgr.getAnalyzerOptions().getBooleanOption("PureOnly", false, checker);
+ mgr.getAnalyzerOptions().getCheckerBooleanOption("PureOnly", false,
+ checker);
}
diff --git a/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/lib/StaticAnalyzer/Core/AnalysisManager.cpp
index dc0d3ec8493a..7fb1c09ca049 100644
--- a/lib/StaticAnalyzer/Core/AnalysisManager.cpp
+++ b/lib/StaticAnalyzer/Core/AnalysisManager.cpp
@@ -14,28 +14,33 @@ using namespace ento;
void AnalysisManager::anchor() { }
-AnalysisManager::AnalysisManager(
- ASTContext &ASTCtx, DiagnosticsEngine &diags, const LangOptions &lang,
- const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr,
- ConstraintManagerCreator constraintmgr, CheckerManager *checkerMgr,
- AnalyzerOptions &Options, CodeInjector *injector)
- : AnaCtxMgr(ASTCtx, Options.UnoptimizedCFG,
- Options.includeImplicitDtorsInCFG(),
- /*AddInitializers=*/true, Options.includeTemporaryDtorsInCFG(),
- Options.includeLifetimeInCFG(),
- // Adding LoopExit elements to the CFG is a requirement for loop
- // unrolling.
- Options.includeLoopExitInCFG() || Options.shouldUnrollLoops(),
- Options.includeScopesInCFG(),
- Options.shouldSynthesizeBodies(),
- Options.shouldConditionalizeStaticInitializers(),
- /*addCXXNewAllocator=*/true,
- Options.includeRichConstructorsInCFG(),
- Options.shouldElideConstructors(),
- injector),
- Ctx(ASTCtx), Diags(diags), LangOpts(lang), PathConsumers(PDC),
- CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr),
- CheckerMgr(checkerMgr), options(Options) {
+AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags,
+ const PathDiagnosticConsumers &PDC,
+ StoreManagerCreator storemgr,
+ ConstraintManagerCreator constraintmgr,
+ CheckerManager *checkerMgr,
+ AnalyzerOptions &Options,
+ CodeInjector *injector)
+ : AnaCtxMgr(
+ ASTCtx, Options.UnoptimizedCFG,
+ Options.ShouldIncludeImplicitDtorsInCFG,
+ /*AddInitializers=*/true,
+ Options.ShouldIncludeTemporaryDtorsInCFG,
+ Options.ShouldIncludeLifetimeInCFG,
+ // Adding LoopExit elements to the CFG is a requirement for loop
+ // unrolling.
+ Options.ShouldIncludeLoopExitInCFG ||
+ Options.ShouldUnrollLoops,
+ Options.ShouldIncludeScopesInCFG,
+ Options.ShouldSynthesizeBodies,
+ Options.ShouldConditionalizeStaticInitializers,
+ /*addCXXNewAllocator=*/true,
+ Options.ShouldIncludeRichConstructorsInCFG,
+ Options.ShouldElideConstructors, injector),
+ Ctx(ASTCtx), Diags(diags), LangOpts(ASTCtx.getLangOpts()),
+ PathConsumers(PDC), CreateStoreMgr(storemgr),
+ CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr),
+ options(Options) {
AnaCtxMgr.getCFGBuildOptions().setAllAlwaysAdd();
}
diff --git a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
index 9b2dc32e0600..0588c2bd3d35 100644
--- a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
+++ b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp
@@ -34,7 +34,7 @@ std::vector<StringRef>
AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) {
static const StringRef StaticAnalyzerChecks[] = {
#define GET_CHECKERS
-#define CHECKER(FULLNAME, CLASS, DESCFILE, HELPTEXT, GROUPINDEX, HIDDEN) \
+#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \
FULLNAME,
#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
#undef CHECKER
@@ -49,114 +49,71 @@ AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) {
return Result;
}
-AnalyzerOptions::UserModeKind AnalyzerOptions::getUserMode() {
- if (UserMode == UMK_NotSet) {
- StringRef ModeStr =
- Config.insert(std::make_pair("mode", "deep")).first->second;
- UserMode = llvm::StringSwitch<UserModeKind>(ModeStr)
- .Case("shallow", UMK_Shallow)
- .Case("deep", UMK_Deep)
- .Default(UMK_NotSet);
- assert(UserMode != UMK_NotSet && "User mode is invalid.");
- }
- return UserMode;
-}
-
-AnalyzerOptions::ExplorationStrategyKind
-AnalyzerOptions::getExplorationStrategy() {
- if (ExplorationStrategy == ExplorationStrategyKind::NotSet) {
- StringRef StratStr =
- Config
- .insert(std::make_pair("exploration_strategy", "unexplored_first_queue"))
- .first->second;
- ExplorationStrategy =
- llvm::StringSwitch<ExplorationStrategyKind>(StratStr)
- .Case("dfs", ExplorationStrategyKind::DFS)
- .Case("bfs", ExplorationStrategyKind::BFS)
- .Case("unexplored_first",
- ExplorationStrategyKind::UnexploredFirst)
- .Case("unexplored_first_queue",
- ExplorationStrategyKind::UnexploredFirstQueue)
- .Case("bfs_block_dfs_contents",
- ExplorationStrategyKind::BFSBlockDFSContents)
- .Default(ExplorationStrategyKind::NotSet);
- assert(ExplorationStrategy != ExplorationStrategyKind::NotSet &&
- "User mode is invalid.");
- }
- return ExplorationStrategy;
-}
-
-IPAKind AnalyzerOptions::getIPAMode() {
- if (IPAMode == IPAK_NotSet) {
- // Use the User Mode to set the default IPA value.
- // Note, we have to add the string to the Config map for the ConfigDumper
- // checker to function properly.
- const char *DefaultIPA = nullptr;
- UserModeKind HighLevelMode = getUserMode();
- if (HighLevelMode == UMK_Shallow)
- DefaultIPA = "inlining";
- else if (HighLevelMode == UMK_Deep)
- DefaultIPA = "dynamic-bifurcate";
- assert(DefaultIPA);
-
- // Lookup the ipa configuration option, use the default from User Mode.
- StringRef ModeStr =
- Config.insert(std::make_pair("ipa", DefaultIPA)).first->second;
- IPAKind IPAConfig = llvm::StringSwitch<IPAKind>(ModeStr)
- .Case("none", IPAK_None)
- .Case("basic-inlining", IPAK_BasicInlining)
- .Case("inlining", IPAK_Inlining)
- .Case("dynamic", IPAK_DynamicDispatch)
- .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate)
- .Default(IPAK_NotSet);
- assert(IPAConfig != IPAK_NotSet && "IPA Mode is invalid.");
-
- // Set the member variable.
- IPAMode = IPAConfig;
- }
-
- return IPAMode;
+ExplorationStrategyKind
+AnalyzerOptions::getExplorationStrategy() const {
+ auto K =
+ llvm::StringSwitch<llvm::Optional<ExplorationStrategyKind>>(
+ ExplorationStrategy)
+ .Case("dfs", ExplorationStrategyKind::DFS)
+ .Case("bfs", ExplorationStrategyKind::BFS)
+ .Case("unexplored_first",
+ ExplorationStrategyKind::UnexploredFirst)
+ .Case("unexplored_first_queue",
+ ExplorationStrategyKind::UnexploredFirstQueue)
+ .Case("unexplored_first_location_queue",
+ ExplorationStrategyKind::UnexploredFirstLocationQueue)
+ .Case("bfs_block_dfs_contents",
+ ExplorationStrategyKind::BFSBlockDFSContents)
+ .Default(None);
+ assert(K.hasValue() && "User mode is invalid.");
+ return K.getValue();
+}
+
+IPAKind AnalyzerOptions::getIPAMode() const {
+ auto K = llvm::StringSwitch<llvm::Optional<IPAKind>>(IPAMode)
+ .Case("none", IPAK_None)
+ .Case("basic-inlining", IPAK_BasicInlining)
+ .Case("inlining", IPAK_Inlining)
+ .Case("dynamic", IPAK_DynamicDispatch)
+ .Case("dynamic-bifurcate", IPAK_DynamicDispatchBifurcate)
+ .Default(None);
+ assert(K.hasValue() && "IPA Mode is invalid.");
+
+ return K.getValue();
}
bool
-AnalyzerOptions::mayInlineCXXMemberFunction(CXXInlineableMemberKind K) {
+AnalyzerOptions::mayInlineCXXMemberFunction(
+ CXXInlineableMemberKind Param) const {
if (getIPAMode() < IPAK_Inlining)
return false;
- if (!CXXMemberInliningMode) {
- static const char *ModeKey = "c++-inlining";
-
- StringRef ModeStr =
- Config.insert(std::make_pair(ModeKey, "destructors")).first->second;
+ auto K =
+ llvm::StringSwitch<llvm::Optional<CXXInlineableMemberKind>>(
+ CXXMemberInliningMode)
+ .Case("constructors", CIMK_Constructors)
+ .Case("destructors", CIMK_Destructors)
+ .Case("methods", CIMK_MemberFunctions)
+ .Case("none", CIMK_None)
+ .Default(None);
- CXXInlineableMemberKind &MutableMode =
- const_cast<CXXInlineableMemberKind &>(CXXMemberInliningMode);
-
- MutableMode = llvm::StringSwitch<CXXInlineableMemberKind>(ModeStr)
- .Case("constructors", CIMK_Constructors)
- .Case("destructors", CIMK_Destructors)
- .Case("none", CIMK_None)
- .Case("methods", CIMK_MemberFunctions)
- .Default(CXXInlineableMemberKind());
-
- if (!MutableMode) {
- // FIXME: We should emit a warning here about an unknown inlining kind,
- // but the AnalyzerOptions doesn't have access to a diagnostic engine.
- MutableMode = CIMK_None;
- }
- }
+ assert(K.hasValue() && "Invalid c++ member function inlining mode.");
- return CXXMemberInliningMode >= K;
+ return *K >= Param;
}
-static StringRef toString(bool b) { return b ? "true" : "false"; }
-
-StringRef AnalyzerOptions::getCheckerOption(StringRef CheckerName,
- StringRef OptionName,
- StringRef Default,
- bool SearchInParents) {
+StringRef AnalyzerOptions::getCheckerStringOption(StringRef OptionName,
+ StringRef DefaultVal,
+ const CheckerBase *C,
+ bool SearchInParents) const {
+ assert(C);
// Search for a package option if the option for the checker is not specified
// and search in parents is enabled.
+ StringRef CheckerName = C->getTagDescription();
+
+ assert(!CheckerName.empty() &&
+ "Empty checker name! Make sure the checker object (including it's "
+ "bases!) if fully initialized before calling this function!");
ConfigTable::const_iterator E = Config.end();
do {
ConfigTable::const_iterator I =
@@ -165,331 +122,35 @@ StringRef AnalyzerOptions::getCheckerOption(StringRef CheckerName,
return StringRef(I->getValue());
size_t Pos = CheckerName.rfind('.');
if (Pos == StringRef::npos)
- return Default;
+ return DefaultVal;
CheckerName = CheckerName.substr(0, Pos);
} while (!CheckerName.empty() && SearchInParents);
- return Default;
+ return DefaultVal;
}
-bool AnalyzerOptions::getBooleanOption(StringRef Name, bool DefaultVal,
- const CheckerBase *C,
- bool SearchInParents) {
+bool AnalyzerOptions::getCheckerBooleanOption(StringRef Name, bool DefaultVal,
+ const CheckerBase *C,
+ bool SearchInParents) const {
// FIXME: We should emit a warning here if the value is something other than
// "true", "false", or the empty string (meaning the default value),
// but the AnalyzerOptions doesn't have access to a diagnostic engine.
- StringRef Default = toString(DefaultVal);
- StringRef V =
- C ? getCheckerOption(C->getTagDescription(), Name, Default,
- SearchInParents)
- : StringRef(Config.insert(std::make_pair(Name, Default)).first->second);
- return llvm::StringSwitch<bool>(V)
+ assert(C);
+ return llvm::StringSwitch<bool>(
+ getCheckerStringOption(Name, DefaultVal ? "true" : "false", C,
+ SearchInParents))
.Case("true", true)
.Case("false", false)
.Default(DefaultVal);
}
-bool AnalyzerOptions::getBooleanOption(Optional<bool> &V, StringRef Name,
- bool DefaultVal, const CheckerBase *C,
- bool SearchInParents) {
- if (!V.hasValue())
- V = getBooleanOption(Name, DefaultVal, C, SearchInParents);
- return V.getValue();
-}
-
-bool AnalyzerOptions::includeTemporaryDtorsInCFG() {
- return getBooleanOption(IncludeTemporaryDtorsInCFG,
- "cfg-temporary-dtors",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::includeImplicitDtorsInCFG() {
- return getBooleanOption(IncludeImplicitDtorsInCFG,
- "cfg-implicit-dtors",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::includeLifetimeInCFG() {
- return getBooleanOption(IncludeLifetimeInCFG, "cfg-lifetime",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::includeLoopExitInCFG() {
- return getBooleanOption(IncludeLoopExitInCFG, "cfg-loopexit",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::includeRichConstructorsInCFG() {
- return getBooleanOption(IncludeRichConstructorsInCFG,
- "cfg-rich-constructors",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::includeScopesInCFG() {
- return getBooleanOption(IncludeScopesInCFG,
- "cfg-scopes",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::mayInlineCXXStandardLibrary() {
- return getBooleanOption(InlineCXXStandardLibrary,
- "c++-stdlib-inlining",
- /*Default=*/true);
-}
-
-bool AnalyzerOptions::mayInlineTemplateFunctions() {
- return getBooleanOption(InlineTemplateFunctions,
- "c++-template-inlining",
- /*Default=*/true);
-}
-
-bool AnalyzerOptions::mayInlineCXXAllocator() {
- return getBooleanOption(InlineCXXAllocator,
- "c++-allocator-inlining",
- /*Default=*/true);
-}
-
-bool AnalyzerOptions::mayInlineCXXContainerMethods() {
- return getBooleanOption(InlineCXXContainerMethods,
- "c++-container-inlining",
- /*Default=*/false);
-}
-
-bool AnalyzerOptions::mayInlineCXXSharedPtrDtor() {
- return getBooleanOption(InlineCXXSharedPtrDtor,
- "c++-shared_ptr-inlining",
- /*Default=*/false);
-}
-
-bool AnalyzerOptions::mayInlineCXXTemporaryDtors() {
- return getBooleanOption(InlineCXXTemporaryDtors,
- "c++-temp-dtor-inlining",
- /*Default=*/true);
-}
-
-bool AnalyzerOptions::mayInlineObjCMethod() {
- return getBooleanOption(ObjCInliningMode,
- "objc-inlining",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::shouldSuppressNullReturnPaths() {
- return getBooleanOption(SuppressNullReturnPaths,
- "suppress-null-return-paths",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::shouldAvoidSuppressingNullArgumentPaths() {
- return getBooleanOption(AvoidSuppressingNullArgumentPaths,
- "avoid-suppressing-null-argument-paths",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::shouldSuppressInlinedDefensiveChecks() {
- return getBooleanOption(SuppressInlinedDefensiveChecks,
- "suppress-inlined-defensive-checks",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::shouldSuppressFromCXXStandardLibrary() {
- return getBooleanOption(SuppressFromCXXStandardLibrary,
- "suppress-c++-stdlib",
- /* Default = */ true);
-}
-
-bool AnalyzerOptions::shouldCrosscheckWithZ3() {
- return getBooleanOption(CrosscheckWithZ3,
- "crosscheck-with-z3",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::shouldReportIssuesInMainSourceFile() {
- return getBooleanOption(ReportIssuesInMainSourceFile,
- "report-in-main-source-file",
- /* Default = */ false);
-}
-
-
-bool AnalyzerOptions::shouldWriteStableReportFilename() {
- return getBooleanOption(StableReportFilename,
- "stable-report-filename",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::shouldSerializeStats() {
- return getBooleanOption(SerializeStats,
- "serialize-stats",
- /* Default = */ false);
-}
-
-bool AnalyzerOptions::shouldElideConstructors() {
- return getBooleanOption(ElideConstructors,
- "elide-constructors",
- /* Default = */ true);
-}
-
-int AnalyzerOptions::getOptionAsInteger(StringRef Name, int DefaultVal,
+int AnalyzerOptions::getCheckerIntegerOption(StringRef Name, int DefaultVal,
const CheckerBase *C,
- bool SearchInParents) {
- SmallString<10> StrBuf;
- llvm::raw_svector_ostream OS(StrBuf);
- OS << DefaultVal;
-
- StringRef V = C ? getCheckerOption(C->getTagDescription(), Name, OS.str(),
- SearchInParents)
- : StringRef(Config.insert(std::make_pair(Name, OS.str()))
- .first->second);
-
- int Res = DefaultVal;
- bool b = V.getAsInteger(10, Res);
- assert(!b && "analyzer-config option should be numeric");
- (void)b;
- return Res;
-}
-
-StringRef AnalyzerOptions::getOptionAsString(StringRef Name,
- StringRef DefaultVal,
- const CheckerBase *C,
- bool SearchInParents) {
- return C ? getCheckerOption(C->getTagDescription(), Name, DefaultVal,
- SearchInParents)
- : StringRef(
- Config.insert(std::make_pair(Name, DefaultVal)).first->second);
-}
-
-unsigned AnalyzerOptions::getAlwaysInlineSize() {
- if (!AlwaysInlineSize.hasValue())
- AlwaysInlineSize = getOptionAsInteger("ipa-always-inline-size", 3);
- return AlwaysInlineSize.getValue();
-}
-
-unsigned AnalyzerOptions::getMaxInlinableSize() {
- if (!MaxInlinableSize.hasValue()) {
- int DefaultValue = 0;
- UserModeKind HighLevelMode = getUserMode();
- switch (HighLevelMode) {
- default:
- llvm_unreachable("Invalid mode.");
- case UMK_Shallow:
- DefaultValue = 4;
- break;
- case UMK_Deep:
- DefaultValue = 100;
- break;
- }
-
- MaxInlinableSize = getOptionAsInteger("max-inlinable-size", DefaultValue);
- }
- return MaxInlinableSize.getValue();
-}
-
-unsigned AnalyzerOptions::getGraphTrimInterval() {
- if (!GraphTrimInterval.hasValue())
- GraphTrimInterval = getOptionAsInteger("graph-trim-interval", 1000);
- return GraphTrimInterval.getValue();
-}
-
-unsigned AnalyzerOptions::getMaxSymbolComplexity() {
- if (!MaxSymbolComplexity.hasValue())
- MaxSymbolComplexity = getOptionAsInteger("max-symbol-complexity", 35);
- return MaxSymbolComplexity.getValue();
-}
-
-unsigned AnalyzerOptions::getMaxTimesInlineLarge() {
- if (!MaxTimesInlineLarge.hasValue())
- MaxTimesInlineLarge = getOptionAsInteger("max-times-inline-large", 32);
- return MaxTimesInlineLarge.getValue();
-}
-
-unsigned AnalyzerOptions::getMinCFGSizeTreatFunctionsAsLarge() {
- if (!MinCFGSizeTreatFunctionsAsLarge.hasValue())
- MinCFGSizeTreatFunctionsAsLarge = getOptionAsInteger(
- "min-cfg-size-treat-functions-as-large", 14);
- return MinCFGSizeTreatFunctionsAsLarge.getValue();
-}
-
-unsigned AnalyzerOptions::getMaxNodesPerTopLevelFunction() {
- if (!MaxNodesPerTopLevelFunction.hasValue()) {
- int DefaultValue = 0;
- UserModeKind HighLevelMode = getUserMode();
- switch (HighLevelMode) {
- default:
- llvm_unreachable("Invalid mode.");
- case UMK_Shallow:
- DefaultValue = 75000;
- break;
- case UMK_Deep:
- DefaultValue = 225000;
- break;
- }
- MaxNodesPerTopLevelFunction = getOptionAsInteger("max-nodes", DefaultValue);
- }
- return MaxNodesPerTopLevelFunction.getValue();
-}
-
-bool AnalyzerOptions::shouldSynthesizeBodies() {
- return getBooleanOption("faux-bodies", true);
-}
-
-bool AnalyzerOptions::shouldPrunePaths() {
- return getBooleanOption("prune-paths", true);
-}
-
-bool AnalyzerOptions::shouldConditionalizeStaticInitializers() {
- return getBooleanOption("cfg-conditional-static-initializers", true);
-}
-
-bool AnalyzerOptions::shouldInlineLambdas() {
- if (!InlineLambdas.hasValue())
- InlineLambdas = getBooleanOption("inline-lambdas", /*Default=*/true);
- return InlineLambdas.getValue();
-}
-
-bool AnalyzerOptions::shouldWidenLoops() {
- if (!WidenLoops.hasValue())
- WidenLoops = getBooleanOption("widen-loops", /*Default=*/false);
- return WidenLoops.getValue();
-}
-
-bool AnalyzerOptions::shouldUnrollLoops() {
- if (!UnrollLoops.hasValue())
- UnrollLoops = getBooleanOption("unroll-loops", /*Default=*/false);
- return UnrollLoops.getValue();
-}
-
-bool AnalyzerOptions::shouldDisplayNotesAsEvents() {
- if (!DisplayNotesAsEvents.hasValue())
- DisplayNotesAsEvents =
- getBooleanOption("notes-as-events", /*Default=*/false);
- return DisplayNotesAsEvents.getValue();
-}
-
-bool AnalyzerOptions::shouldAggressivelySimplifyBinaryOperation() {
- if (!AggressiveBinaryOperationSimplification.hasValue())
- AggressiveBinaryOperationSimplification =
- getBooleanOption("aggressive-binary-operation-simplification",
- /*Default=*/false);
- return AggressiveBinaryOperationSimplification.getValue();
-}
-
-StringRef AnalyzerOptions::getCTUDir() {
- if (!CTUDir.hasValue()) {
- CTUDir = getOptionAsString("ctu-dir", "");
- if (!llvm::sys::fs::is_directory(*CTUDir))
- CTUDir = "";
- }
- return CTUDir.getValue();
-}
-
-bool AnalyzerOptions::naiveCTUEnabled() {
- if (!NaiveCTU.hasValue()) {
- NaiveCTU = getBooleanOption("experimental-enable-naive-ctu-analysis",
- /*Default=*/false);
- }
- return NaiveCTU.getValue();
-}
-
-StringRef AnalyzerOptions::getCTUIndexName() {
- if (!CTUIndexName.hasValue())
- CTUIndexName = getOptionAsString("ctu-index-name", "externalFnMap.txt");
- return CTUIndexName.getValue();
+ bool SearchInParents) const {
+ int Ret = DefaultVal;
+ bool HasFailed = getCheckerStringOption(Name, std::to_string(DefaultVal), C,
+ SearchInParents)
+ .getAsInteger(10, Ret);
+ assert(!HasFailed && "analyzer-config option should be numeric");
+ (void)HasFailed;
+ return Ret;
}
diff --git a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
index db4c1432ccc3..d8ed6942de81 100644
--- a/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
+++ b/lib/StaticAnalyzer/Core/BasicValueFactory.cpp
@@ -207,7 +207,7 @@ BasicValueFactory::evalAPSInt(BinaryOperator::Opcode Op,
const llvm::APSInt& V1, const llvm::APSInt& V2) {
switch (Op) {
default:
- assert(false && "Invalid Opcode.");
+ llvm_unreachable("Invalid Opcode.");
case BO_Mul:
return &getValue( V1 * V2 );
diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp
index f990eb6a058d..fd7f53210490 100644
--- a/lib/StaticAnalyzer/Core/BugReporter.cpp
+++ b/lib/StaticAnalyzer/Core/BugReporter.cpp
@@ -546,7 +546,8 @@ static void updateStackPiecesWithMessage(PathDiagnosticPiece &P,
}
}
-static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM);
+static void CompactMacroExpandedPieces(PathPieces &path,
+ const SourceManager& SM);
std::shared_ptr<PathDiagnosticControlFlowPiece> generateDiagForSwitchOP(
@@ -819,7 +820,7 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE,
// and values by tracing interesting calculations backwards through evaluated
// expressions along a path. This is probably overly complicated, but the idea
// is that if an expression computed an "interesting" value, the child
-// expressions are are also likely to be "interesting" as well (which then
+// expressions are also likely to be "interesting" as well (which then
// propagates to the values they in turn compute). This reverse propagation
// is needed to track interesting correlations across function call boundaries,
// where formal arguments bind to actual arguments, etc. This is also needed
@@ -841,7 +842,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R,
default:
if (!isa<CastExpr>(Ex))
break;
- // Fall through.
+ LLVM_FALLTHROUGH;
case Stmt::BinaryOperatorClass:
case Stmt::UnaryOperatorClass: {
for (const Stmt *SubStmt : Ex->children()) {
@@ -861,8 +862,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R,
static void reversePropagateInterestingSymbols(BugReport &R,
InterestingExprs &IE,
const ProgramState *State,
- const LocationContext *CalleeCtx,
- const LocationContext *CallerCtx)
+ const LocationContext *CalleeCtx)
{
// FIXME: Handle non-CallExpr-based CallEvents.
const StackFrameContext *Callee = CalleeCtx->getStackFrame();
@@ -967,8 +967,7 @@ static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) {
/// Adds a sanitized control-flow diagnostic edge to a path.
static void addEdgeToPath(PathPieces &path,
PathDiagnosticLocation &PrevLoc,
- PathDiagnosticLocation NewLoc,
- const LocationContext *LC) {
+ PathDiagnosticLocation NewLoc) {
if (!NewLoc.isValid())
return;
@@ -1043,7 +1042,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
// not from declaration.
if (D->hasBody())
addEdgeToPath(PD.getActivePath(), PrevLoc,
- PathDiagnosticLocation::createBegin(D, SM), CalleeLC);
+ PathDiagnosticLocation::createBegin(D, SM));
}
// Did we visit an entire call?
@@ -1108,7 +1107,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
// We are descending into a call (backwards). Construct
// a new call piece to contain the path pieces for that call.
- auto C = PathDiagnosticCallPiece::construct(N, *CE, SM);
+ auto C = PathDiagnosticCallPiece::construct(*CE, SM);
// Record the mapping from call piece to LocationContext.
LCM[&C->path] = CE->getCalleeContext();
@@ -1121,7 +1120,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
N->getLocationContext());
}
// Add the edge to the return site.
- addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn, PDB.LC);
+ addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn);
PrevLoc.invalidate();
}
@@ -1151,7 +1150,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
if (!isa<ObjCForCollectionStmt>(PS->getStmt())) {
PathDiagnosticLocation L =
PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC);
- addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC);
+ addEdgeToPath(PD.getActivePath(), PrevLoc, L);
}
} else if (auto BE = P.getAs<BlockEdge>()) {
@@ -1168,8 +1167,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
const LocationContext *CalleeCtx = PDB.LC;
if (CallerCtx != CalleeCtx && AddPathEdges) {
reversePropagateInterestingSymbols(*PDB.getBugReport(), IE,
- N->getState().get(),
- CalleeCtx, CallerCtx);
+ N->getState().get(), CalleeCtx);
}
}
@@ -1194,13 +1192,12 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
"of the loop");
p->setPrunable(true);
- addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation(), PDB.LC);
+ addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation());
PD.getActivePath().push_front(std::move(p));
if (const auto *CS = dyn_cast_or_null<CompoundStmt>(Body)) {
addEdgeToPath(PD.getActivePath(), PrevLoc,
- PathDiagnosticLocation::createEndBrace(CS, SM),
- PDB.LC);
+ PathDiagnosticLocation::createEndBrace(CS, SM));
}
}
@@ -1236,13 +1233,13 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N,
auto PE = std::make_shared<PathDiagnosticEventPiece>(L, str);
PE->setPrunable(true);
addEdgeToPath(PD.getActivePath(), PrevLoc,
- PE->getLocation(), PDB.LC);
+ PE->getLocation());
PD.getActivePath().push_front(std::move(PE));
}
} else if (isa<BreakStmt>(Term) || isa<ContinueStmt>(Term) ||
isa<GotoStmt>(Term)) {
PathDiagnosticLocation L(Term, SM, PDB.LC);
- addEdgeToPath(PD.getActivePath(), PrevLoc, L, PDB.LC);
+ addEdgeToPath(PD.getActivePath(), PrevLoc, L);
}
}
}
@@ -1269,7 +1266,7 @@ static const Stmt *getStmtParent(const Stmt *S, const ParentMap &PM) {
if (!S)
break;
- if (isa<ExprWithCleanups>(S) ||
+ if (isa<FullExpr>(S) ||
isa<CXXBindTemporaryExpr>(S) ||
isa<SubstNonTypeTemplateParmExpr>(S))
continue;
@@ -1540,8 +1537,7 @@ static Optional<size_t> getLengthOnSingleLine(SourceManager &SM,
/// - if there is an inlined call between the edges instead of a single event.
/// - if the whole statement is large enough that having subexpression arrows
/// might be helpful.
-static void removeContextCycles(PathPieces &Path, SourceManager &SM,
- ParentMap &PM) {
+static void removeContextCycles(PathPieces &Path, SourceManager &SM) {
for (PathPieces::iterator I = Path.begin(), E = Path.end(); I != E; ) {
// Pattern match the current piece and its successor.
const auto *PieceI = dyn_cast<PathDiagnosticControlFlowPiece>(I->get());
@@ -1632,8 +1628,8 @@ static void removePunyEdges(PathPieces &path, SourceManager &SM,
if (isConditionForTerminator(end, endParent))
continue;
- SourceLocation FirstLoc = start->getLocStart();
- SourceLocation SecondLoc = end->getLocStart();
+ SourceLocation FirstLoc = start->getBeginLoc();
+ SourceLocation SecondLoc = end->getBeginLoc();
if (!SM.isWrittenInSameFile(FirstLoc, SecondLoc))
continue;
@@ -1844,7 +1840,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM,
// and aesthetically pleasing.
addContextEdges(path, SM, PM, LC);
// Remove "cyclical" edges that include one or more context edges.
- removeContextCycles(path, SM, PM);
+ removeContextCycles(path, SM);
// Hoist edges originating from branch conditions to branches
// for simple branches.
simplifySimpleBranches(path);
@@ -1881,6 +1877,22 @@ static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM,
using VisitorsDiagnosticsTy = llvm::DenseMap<const ExplodedNode *,
std::vector<std::shared_ptr<PathDiagnosticPiece>>>;
+/// Populate executes lines with lines containing at least one diagnostics.
+static void updateExecutedLinesWithDiagnosticPieces(
+ PathDiagnostic &PD) {
+
+ PathPieces path = PD.path.flatten(/*ShouldFlattenMacros=*/true);
+ FilesToLineNumsMap &ExecutedLines = PD.getExecutedLines();
+
+ for (const auto &P : path) {
+ FullSourceLoc Loc = P->getLocation().asLocation().getExpansionLoc();
+ FileID FID = Loc.getFileID();
+ unsigned LineNo = Loc.getLineNumber();
+ assert(FID.isValid());
+ ExecutedLines[FID].insert(LineNo);
+ }
+}
+
/// This function is responsible for generating diagnostic pieces that are
/// *not* provided by bug report visitors.
/// These diagnostics may differ depending on the consumer's settings,
@@ -1946,8 +1958,7 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer(
continue;
if (AddPathEdges)
- addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation(),
- PDB.LC);
+ addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation());
updateStackPiecesWithMessage(*Note, CallStack);
PD->getActivePath().push_front(Note);
}
@@ -1959,15 +1970,13 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer(
const StackFrameContext *CalleeLC = PDB.LC->getStackFrame();
const Decl *D = CalleeLC->getDecl();
addEdgeToPath(PD->getActivePath(), PrevLoc,
- PathDiagnosticLocation::createBegin(D, SM), CalleeLC);
+ PathDiagnosticLocation::createBegin(D, SM));
}
- if (!AddPathEdges && GenerateDiagnostics)
- CompactPathDiagnostic(PD->getMutablePieces(), SM);
// Finally, prune the diagnostic path of uninteresting stuff.
if (!PD->path.empty()) {
- if (R->shouldPrunePath() && Opts.shouldPrunePaths()) {
+ if (R->shouldPrunePath() && Opts.ShouldPrunePaths) {
bool stillHasNotes =
removeUnneededCalls(PD->getMutablePieces(), R, LCM);
assert(stillHasNotes);
@@ -1997,6 +2006,10 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer(
removeRedundantMsgs(PD->getMutablePieces());
removeEdgesToDefaultInitializers(PD->getMutablePieces());
}
+
+ if (GenerateDiagnostics && Opts.ShouldDisplayMacroExpansions)
+ CompactMacroExpandedPieces(PD->getMutablePieces(), SM);
+
return PD;
}
@@ -2007,8 +2020,6 @@ static std::unique_ptr<PathDiagnostic> generatePathDiagnosticForConsumer(
void BugType::anchor() {}
-void BugType::FlushReports(BugReporter &BR) {}
-
void BuiltinBug::anchor() {}
//===----------------------------------------------------------------------===//
@@ -2237,14 +2248,6 @@ void BugReporter::FlushReports() {
if (BugTypes.isEmpty())
return;
- // First flush the warnings for each BugType. This may end up creating new
- // warnings and new BugTypes.
- // FIXME: Only NSErrorChecker needs BugType's FlushReports.
- // Turn NSErrorChecker into a proper checker and remove this.
- SmallVector<const BugType *, 16> bugTypes(BugTypes.begin(), BugTypes.end());
- for (const auto I : bugTypes)
- const_cast<BugType*>(I)->FlushReports(*this);
-
// We need to flush reports in deterministic order to ensure the order
// of the reports is consistent between runs.
for (const auto EQ : EQClassesVector)
@@ -2380,8 +2383,7 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph,
}
// Sort the error paths from longest to shortest.
- llvm::sort(ReportNodes.begin(), ReportNodes.end(),
- PriorityCompare<true>(PriorityMap));
+ llvm::sort(ReportNodes, PriorityCompare<true>(PriorityMap));
}
bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) {
@@ -2437,9 +2439,10 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) {
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<std::shared_ptr<PathDiagnosticMacroPiece>, SourceLocation>>;
@@ -2455,7 +2458,7 @@ static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) {
// Recursively compact calls.
if (auto *call = dyn_cast<PathDiagnosticCallPiece>(&*piece)) {
- CompactPathDiagnostic(call->path, SM);
+ CompactMacroExpandedPieces(call->path, SM);
}
// Get the location of the PathDiagnosticPiece.
@@ -2569,7 +2572,7 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode,
}
for (auto &V : visitors) {
- auto P = V->VisitNode(NextNode, Pred, BRC, *R);
+ auto P = V->VisitNode(NextNode, BRC, *R);
if (P)
(*Notes)[NextNode].push_back(std::move(P));
}
@@ -2618,7 +2621,7 @@ std::pair<BugReport*, std::unique_ptr<VisitorsDiagnosticsTy>> findValidReport(
generateVisitorsDiagnostics(R, ErrorNode, BRC);
if (R->isValid()) {
- if (Opts.shouldCrosscheckWithZ3()) {
+ if (Opts.ShouldCrosscheckWithZ3) {
// If crosscheck is enabled, remove all visitors, add the refutation
// visitor and check again
R->clearVisitors();
@@ -2806,16 +2809,15 @@ static bool isInevitablySinking(const ExplodedNode *N) {
DFSWorkList.pop_back();
Visited.insert(Blk);
+ // If at least one path reaches the CFG exit, it means that control is
+ // returned to the caller. For now, say that we are not sure what
+ // happens next. If necessary, this can be improved to analyze
+ // the parent StackFrameContext's call site in a similar manner.
+ if (Blk == &Cfg.getExit())
+ return false;
+
for (const auto &Succ : Blk->succs()) {
if (const CFGBlock *SuccBlk = Succ.getReachableBlock()) {
- if (SuccBlk == &Cfg.getExit()) {
- // If at least one path reaches the CFG exit, it means that control is
- // returned to the caller. For now, say that we are not sure what
- // happens next. If necessary, this can be improved to analyze
- // the parent StackFrameContext's call site in a similar manner.
- return false;
- }
-
if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) {
// If the block has reachable child blocks that aren't no-return,
// add them to the worklist.
@@ -2961,7 +2963,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) {
}
PathPieces &Pieces = PD->getMutablePieces();
- if (getAnalyzerOptions().shouldDisplayNotesAsEvents()) {
+ if (getAnalyzerOptions().ShouldDisplayNotesAsEvents) {
// For path diagnostic consumers that don't support extra notes,
// we may optionally convert those to path notes.
for (auto I = report->getNotes().rbegin(),
@@ -2985,6 +2987,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) {
for (const auto &i : Meta)
PD->addMeta(i);
+ updateExecutedLinesWithDiagnosticPieces(*PD);
Consumer->HandlePathDiagnostic(std::move(PD));
}
}
@@ -2993,7 +2996,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) {
/// into \p ExecutedLines.
static void populateExecutedLinesWithFunctionSignature(
const Decl *Signature, SourceManager &SM,
- std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) {
+ FilesToLineNumsMap &ExecutedLines) {
SourceRange SignatureSourceRange;
const Stmt* Body = Signature->getBody();
if (const auto FD = dyn_cast<FunctionDecl>(Signature)) {
@@ -3006,22 +3009,26 @@ static void populateExecutedLinesWithFunctionSignature(
SourceLocation Start = SignatureSourceRange.getBegin();
SourceLocation End = Body ? Body->getSourceRange().getBegin()
: SignatureSourceRange.getEnd();
+ if (!Start.isValid() || !End.isValid())
+ return;
unsigned StartLine = SM.getExpansionLineNumber(Start);
unsigned EndLine = SM.getExpansionLineNumber(End);
FileID FID = SM.getFileID(SM.getExpansionLoc(Start));
for (unsigned Line = StartLine; Line <= EndLine; Line++)
- ExecutedLines->operator[](FID.getHashValue()).insert(Line);
+ ExecutedLines[FID].insert(Line);
}
static void populateExecutedLinesWithStmt(
const Stmt *S, SourceManager &SM,
- std::unique_ptr<FilesToLineNumsMap> &ExecutedLines) {
+ FilesToLineNumsMap &ExecutedLines) {
SourceLocation Loc = S->getSourceRange().getBegin();
+ if (!Loc.isValid())
+ return;
SourceLocation ExpansionLoc = SM.getExpansionLoc(Loc);
FileID FID = SM.getFileID(ExpansionLoc);
unsigned LineNo = SM.getExpansionLineNumber(ExpansionLoc);
- ExecutedLines->operator[](FID.getHashValue()).insert(LineNo);
+ ExecutedLines[FID].insert(LineNo);
}
/// \return all executed lines including function signatures on the path
@@ -3034,13 +3041,13 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) {
if (N->getFirstPred() == nullptr) {
// First node: show signature of the entrance point.
const Decl *D = N->getLocationContext()->getDecl();
- populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines);
+ populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines);
} else if (auto CE = N->getLocationAs<CallEnter>()) {
// Inlined function: show signature.
const Decl* D = CE->getCalleeContext()->getDecl();
- populateExecutedLinesWithFunctionSignature(D, SM, ExecutedLines);
+ populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines);
} else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) {
- populateExecutedLinesWithStmt(S, SM, ExecutedLines);
+ populateExecutedLinesWithStmt(S, SM, *ExecutedLines);
// Show extra context for some parent kinds.
const Stmt *P = N->getParentMap().getParent(S);
@@ -3049,12 +3056,12 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) {
// return statement is generated, but we do want to show the whole
// return.
if (const auto *RS = dyn_cast_or_null<ReturnStmt>(P)) {
- populateExecutedLinesWithStmt(RS, SM, ExecutedLines);
+ populateExecutedLinesWithStmt(RS, SM, *ExecutedLines);
P = N->getParentMap().getParent(RS);
}
if (P && (isa<SwitchCase>(P) || isa<LabelStmt>(P)))
- populateExecutedLinesWithStmt(P, SM, ExecutedLines);
+ populateExecutedLinesWithStmt(P, SM, *ExecutedLines);
}
N = N->getFirstPred();
@@ -3093,7 +3100,7 @@ BugReporter::generateDiagnosticForConsumerMap(
// report location to the last piece in the main source file.
AnalyzerOptions &Opts = getAnalyzerOptions();
for (auto const &P : *Out)
- if (Opts.shouldReportIssuesInMainSourceFile() && !Opts.AnalyzeAll)
+ if (Opts.ShouldReportIssuesInMainSourceFile && !Opts.AnalyzeAll)
P.second->resetDiagnosticLocationToMainFile();
return Out;
diff --git a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
index d4d33c1746ce..da94b6eb21e9 100644
--- a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
+++ b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp
@@ -42,10 +42,10 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/None.h"
#include "llvm/ADT/Optional.h"
@@ -71,12 +71,6 @@ using namespace ento;
// Utility functions.
//===----------------------------------------------------------------------===//
-bool bugreporter::isDeclRefExprToReference(const Expr *E) {
- if (const auto *DRE = dyn_cast<DeclRefExpr>(E))
- return DRE->getDecl()->getType()->isReferenceType();
- return false;
-}
-
static const Expr *peelOffPointerArithmetic(const BinaryOperator *B) {
if (B->isAdditiveOp() && B->getType()->isPointerType()) {
if (B->getLHS()->getType()->isPointerType()) {
@@ -142,8 +136,8 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) {
E = AE->getBase();
} else if (const auto *PE = dyn_cast<ParenExpr>(E)) {
E = PE->getSubExpr();
- } else if (const auto *EWC = dyn_cast<ExprWithCleanups>(E)) {
- E = EWC->getSubExpr();
+ } else if (const auto *FE = dyn_cast<FullExpr>(E)) {
+ E = FE->getSubExpr();
} else {
// Other arbitrary stuff.
break;
@@ -160,34 +154,19 @@ const Expr *bugreporter::getDerefExpr(const Stmt *S) {
return E;
}
-const Stmt *bugreporter::GetDenomExpr(const ExplodedNode *N) {
- const Stmt *S = N->getLocationAs<PreStmt>()->getStmt();
- if (const auto *BE = dyn_cast<BinaryOperator>(S))
- return BE->getRHS();
- return nullptr;
-}
-
-const Stmt *bugreporter::GetRetValExpr(const ExplodedNode *N) {
- const Stmt *S = N->getLocationAs<PostStmt>()->getStmt();
- if (const auto *RS = dyn_cast<ReturnStmt>(S))
- return RS->getRetValue();
- return nullptr;
-}
-
//===----------------------------------------------------------------------===//
// Definitions for bug reporter visitors.
//===----------------------------------------------------------------------===//
std::shared_ptr<PathDiagnosticPiece>
-BugReporterVisitor::getEndPath(BugReporterContext &BRC,
- const ExplodedNode *EndPathNode, BugReport &BR) {
+BugReporterVisitor::getEndPath(BugReporterContext &,
+ const ExplodedNode *, BugReport &) {
return nullptr;
}
void
-BugReporterVisitor::finalizeVisitor(BugReporterContext &BRC,
- const ExplodedNode *EndPathNode,
- BugReport &BR) {}
+BugReporterVisitor::finalizeVisitor(BugReporterContext &,
+ const ExplodedNode *, BugReport &) {}
std::shared_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath(
BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) {
@@ -269,10 +248,14 @@ namespace {
/// pointer dereference outside.
class NoStoreFuncVisitor final : public BugReporterVisitor {
const SubRegion *RegionOfInterest;
+ MemRegionManager &MmrMgr;
const SourceManager &SM;
const PrintingPolicy &PP;
- static constexpr const char *DiagnosticsMsg =
- "Returning without writing to '";
+
+ /// Recursion limit for dereferencing fields when looking for the
+ /// region of interest.
+ /// The limit of two indicates that we will dereference fields only once.
+ static const unsigned DEREFERENCE_LIMIT = 2;
/// Frames writing into \c RegionOfInterest.
/// This visitor generates a note only if a function does not write into
@@ -285,21 +268,22 @@ class NoStoreFuncVisitor final : public BugReporterVisitor {
llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingRegion;
llvm::SmallPtrSet<const StackFrameContext *, 32> FramesModifyingCalculated;
+ using RegionVector = SmallVector<const MemRegion *, 5>;
public:
NoStoreFuncVisitor(const SubRegion *R)
- : RegionOfInterest(R),
- SM(R->getMemRegionManager()->getContext().getSourceManager()),
- PP(R->getMemRegionManager()->getContext().getPrintingPolicy()) {}
+ : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()),
+ SM(MmrMgr.getContext().getSourceManager()),
+ PP(MmrMgr.getContext().getPrintingPolicy()) {}
void Profile(llvm::FoldingSetNodeID &ID) const override {
static int Tag = 0;
ID.AddPointer(&Tag);
+ ID.AddPointer(RegionOfInterest);
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC,
- BugReport &BR) override {
+ BugReporterContext &BR,
+ BugReport &) override {
const LocationContext *Ctx = N->getLocationContext();
const StackFrameContext *SCtx = Ctx->getStackFrame();
@@ -307,48 +291,66 @@ public:
auto CallExitLoc = N->getLocationAs<CallExitBegin>();
// No diagnostic if region was modified inside the frame.
- if (!CallExitLoc)
+ if (!CallExitLoc || isRegionOfInterestModifiedInFrame(N))
return nullptr;
CallEventRef<> Call =
- BRC.getStateManager().getCallEventManager().getCaller(SCtx, State);
+ BR.getStateManager().getCallEventManager().getCaller(SCtx, State);
+
+ if (SM.isInSystemHeader(Call->getDecl()->getSourceRange().getBegin()))
+ return nullptr;
// Region of interest corresponds to an IVar, exiting a method
// which could have written into that IVar, but did not.
- if (const auto *MC = dyn_cast<ObjCMethodCall>(Call))
- if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest))
- if (potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(),
- IvarR->getDecl()) &&
- !isRegionOfInterestModifiedInFrame(N))
- return notModifiedMemberDiagnostics(
- Ctx, *CallExitLoc, Call, MC->getReceiverSVal().getAsRegion());
+ if (const auto *MC = dyn_cast<ObjCMethodCall>(Call)) {
+ if (const auto *IvarR = dyn_cast<ObjCIvarRegion>(RegionOfInterest)) {
+ const MemRegion *SelfRegion = MC->getReceiverSVal().getAsRegion();
+ if (RegionOfInterest->isSubRegionOf(SelfRegion) &&
+ potentiallyWritesIntoIvar(Call->getRuntimeDefinition().getDecl(),
+ IvarR->getDecl()))
+ return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, SelfRegion,
+ "self", /*FirstIsReferenceType=*/false,
+ 1);
+ }
+ }
if (const auto *CCall = dyn_cast<CXXConstructorCall>(Call)) {
const MemRegion *ThisR = CCall->getCXXThisVal().getAsRegion();
if (RegionOfInterest->isSubRegionOf(ThisR)
- && !CCall->getDecl()->isImplicit()
- && !isRegionOfInterestModifiedInFrame(N))
- return notModifiedMemberDiagnostics(Ctx, *CallExitLoc, Call, ThisR);
+ && !CCall->getDecl()->isImplicit())
+ return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, ThisR,
+ "this",
+ /*FirstIsReferenceType=*/false, 1);
+
+ // Do not generate diagnostics for not modified parameters in
+ // constructors.
+ return nullptr;
}
ArrayRef<ParmVarDecl *> parameters = getCallParameters(Call);
for (unsigned I = 0; I < Call->getNumArgs() && I < parameters.size(); ++I) {
const ParmVarDecl *PVD = parameters[I];
SVal S = Call->getArgSVal(I);
- unsigned IndirectionLevel = 1;
+ bool ParamIsReferenceType = PVD->getType()->isReferenceType();
+ std::string ParamName = PVD->getNameAsString();
+
+ int IndirectionLevel = 1;
QualType T = PVD->getType();
while (const MemRegion *R = S.getAsRegion()) {
- if (RegionOfInterest->isSubRegionOf(R)
- && !isPointerToConst(PVD->getType())) {
+ if (RegionOfInterest->isSubRegionOf(R) && !isPointerToConst(T))
+ return notModifiedDiagnostics(Ctx, *CallExitLoc, Call, {}, R,
+ ParamName, ParamIsReferenceType,
+ IndirectionLevel);
- if (isRegionOfInterestModifiedInFrame(N))
- return nullptr;
-
- return notModifiedParameterDiagnostics(
- Ctx, *CallExitLoc, Call, PVD, R, IndirectionLevel);
- }
QualType PT = T->getPointeeType();
if (PT.isNull() || PT->isVoidType()) break;
+
+ if (const RecordDecl *RD = PT->getAsRecordDecl())
+ if (auto P = findRegionOfInterestInRecord(RD, State, R))
+ return notModifiedDiagnostics(
+ Ctx, *CallExitLoc, Call, *P, RegionOfInterest, ParamName,
+ ParamIsReferenceType, IndirectionLevel);
+
S = State->getSVal(R, PT);
T = PT;
IndirectionLevel++;
@@ -359,20 +361,94 @@ public:
}
private:
+ /// Attempts to find the region of interest in a given CXX decl,
+ /// by either following the base classes or fields.
+ /// Dereferences fields up to a given recursion limit.
+ /// Note that \p Vec is passed by value, leading to quadratic copying cost,
+ /// but it's OK in practice since its length is limited to DEREFERENCE_LIMIT.
+ /// \return A chain fields leading to the region of interest or None.
+ const Optional<RegionVector>
+ findRegionOfInterestInRecord(const RecordDecl *RD, ProgramStateRef State,
+ const MemRegion *R,
+ const RegionVector &Vec = {},
+ int depth = 0) {
+
+ if (depth == DEREFERENCE_LIMIT) // Limit the recursion depth.
+ return None;
+
+ if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD))
+ if (!RDX->hasDefinition())
+ return None;
+
+ // Recursively examine the base classes.
+ // Note that following base classes does not increase the recursion depth.
+ if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD))
+ for (const auto II : RDX->bases())
+ if (const RecordDecl *RRD = II.getType()->getAsRecordDecl())
+ if (auto Out = findRegionOfInterestInRecord(RRD, State, R, Vec, depth))
+ return Out;
+
+ for (const FieldDecl *I : RD->fields()) {
+ QualType FT = I->getType();
+ const FieldRegion *FR = MmrMgr.getFieldRegion(I, cast<SubRegion>(R));
+ const SVal V = State->getSVal(FR);
+ const MemRegion *VR = V.getAsRegion();
+
+ RegionVector VecF = Vec;
+ VecF.push_back(FR);
+
+ if (RegionOfInterest == VR)
+ return VecF;
+
+ if (const RecordDecl *RRD = FT->getAsRecordDecl())
+ if (auto Out =
+ findRegionOfInterestInRecord(RRD, State, FR, VecF, depth + 1))
+ return Out;
+
+ QualType PT = FT->getPointeeType();
+ if (PT.isNull() || PT->isVoidType() || !VR) continue;
+
+ if (const RecordDecl *RRD = PT->getAsRecordDecl())
+ if (auto Out =
+ findRegionOfInterestInRecord(RRD, State, VR, VecF, depth + 1))
+ return Out;
+
+ }
+
+ return None;
+ }
/// \return Whether the method declaration \p Parent
/// syntactically has a binary operation writing into the ivar \p Ivar.
bool potentiallyWritesIntoIvar(const Decl *Parent,
const ObjCIvarDecl *Ivar) {
using namespace ast_matchers;
- if (!Parent || !Parent->getBody())
+ const char * IvarBind = "Ivar";
+ if (!Parent || !Parent->hasBody())
return false;
StatementMatcher WriteIntoIvarM = binaryOperator(
- hasOperatorName("="), hasLHS(ignoringParenImpCasts(objcIvarRefExpr(
- hasDeclaration(equalsNode(Ivar))))));
+ hasOperatorName("="),
+ hasLHS(ignoringParenImpCasts(
+ objcIvarRefExpr(hasDeclaration(equalsNode(Ivar))).bind(IvarBind))));
StatementMatcher ParentM = stmt(hasDescendant(WriteIntoIvarM));
auto Matches = match(ParentM, *Parent->getBody(), Parent->getASTContext());
- return !Matches.empty();
+ for (BoundNodes &Match : Matches) {
+ auto IvarRef = Match.getNodeAs<ObjCIvarRefExpr>(IvarBind);
+ if (IvarRef->isFreeIvar())
+ return true;
+
+ const Expr *Base = IvarRef->getBase();
+ if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Base))
+ Base = ICE->getSubExpr();
+
+ if (const auto *DRE = dyn_cast<DeclRefExpr>(Base))
+ if (const auto *ID = dyn_cast<ImplicitParamDecl>(DRE->getDecl()))
+ if (ID->getParameterKind() == ImplicitParamDecl::ObjCSelf)
+ return true;
+
+ return false;
+ }
+ return false;
}
/// Check and lazily calculate whether the region of interest is
@@ -433,6 +509,8 @@ private:
RuntimeDefinition RD = Call->getRuntimeDefinition();
if (const auto *FD = dyn_cast_or_null<FunctionDecl>(RD.getDecl()))
return FD->parameters();
+ if (const auto *MD = dyn_cast_or_null<ObjCMethodDecl>(RD.getDecl()))
+ return MD->parameters();
return Call->parameters();
}
@@ -443,123 +521,112 @@ private:
Ty->getPointeeType().getCanonicalType().isConstQualified();
}
- /// \return Diagnostics piece for the member field not modified
- /// in a given function.
- std::shared_ptr<PathDiagnosticPiece> notModifiedMemberDiagnostics(
- const LocationContext *Ctx,
- CallExitBegin &CallExitLoc,
- CallEventRef<> Call,
- const MemRegion *ArgRegion) {
- const char *TopRegionName = isa<ObjCMethodCall>(Call) ? "self" : "this";
+ /// \return Diagnostics piece for region not modified in the current function.
+ std::shared_ptr<PathDiagnosticPiece>
+ notModifiedDiagnostics(const LocationContext *Ctx, CallExitBegin &CallExitLoc,
+ CallEventRef<> Call, const RegionVector &FieldChain,
+ const MemRegion *MatchedRegion, StringRef FirstElement,
+ bool FirstIsReferenceType, unsigned IndirectionLevel) {
+
+ PathDiagnosticLocation L;
+ if (const ReturnStmt *RS = CallExitLoc.getReturnStmt()) {
+ L = PathDiagnosticLocation::createBegin(RS, SM, Ctx);
+ } else {
+ L = PathDiagnosticLocation(
+ Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(),
+ SM);
+ }
+
SmallString<256> sbuf;
llvm::raw_svector_ostream os(sbuf);
- os << DiagnosticsMsg;
- bool out = prettyPrintRegionName(TopRegionName, "->", /*IsReference=*/true,
- /*IndirectionLevel=*/1, ArgRegion, os, PP);
+ os << "Returning without writing to '";
- // Return nothing if we have failed to pretty-print.
- if (!out)
+ // Do not generate the note if failed to pretty-print.
+ if (!prettyPrintRegionName(FirstElement, FirstIsReferenceType,
+ MatchedRegion, FieldChain, IndirectionLevel, os))
return nullptr;
os << "'";
- PathDiagnosticLocation L =
- getPathDiagnosticLocation(CallExitLoc.getReturnStmt(), SM, Ctx, Call);
return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
}
- /// \return Diagnostics piece for the parameter \p PVD not modified
- /// in a given function.
- /// \p IndirectionLevel How many times \c ArgRegion has to be dereferenced
- /// before we get to the super region of \c RegionOfInterest
- std::shared_ptr<PathDiagnosticPiece>
- notModifiedParameterDiagnostics(const LocationContext *Ctx,
- CallExitBegin &CallExitLoc,
- CallEventRef<> Call,
- const ParmVarDecl *PVD,
- const MemRegion *ArgRegion,
- unsigned IndirectionLevel) {
- PathDiagnosticLocation L = getPathDiagnosticLocation(
- CallExitLoc.getReturnStmt(), SM, Ctx, Call);
- SmallString<256> sbuf;
- llvm::raw_svector_ostream os(sbuf);
- os << DiagnosticsMsg;
- bool IsReference = PVD->getType()->isReferenceType();
- const char *Sep = IsReference && IndirectionLevel == 1 ? "." : "->";
- bool Success = prettyPrintRegionName(
- PVD->getQualifiedNameAsString().c_str(),
- Sep, IsReference, IndirectionLevel, ArgRegion, os, PP);
-
- // Print the parameter name if the pretty-printing has failed.
- if (!Success)
- PVD->printQualifiedName(os);
- os << "'";
- return std::make_shared<PathDiagnosticEventPiece>(L, os.str());
- }
+ /// Pretty-print region \p MatchedRegion to \p os.
+ /// \return Whether printing succeeded.
+ bool prettyPrintRegionName(StringRef FirstElement, bool FirstIsReferenceType,
+ const MemRegion *MatchedRegion,
+ const RegionVector &FieldChain,
+ int IndirectionLevel,
+ llvm::raw_svector_ostream &os) {
- /// \return a path diagnostic location for the optionally
- /// present return statement \p RS.
- PathDiagnosticLocation getPathDiagnosticLocation(const ReturnStmt *RS,
- const SourceManager &SM,
- const LocationContext *Ctx,
- CallEventRef<> Call) {
- if (RS)
- return PathDiagnosticLocation::createBegin(RS, SM, Ctx);
- return PathDiagnosticLocation(
- Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM);
- }
+ if (FirstIsReferenceType)
+ IndirectionLevel--;
- /// Pretty-print region \p ArgRegion starting from parent to \p os.
- /// \return whether printing has succeeded
- bool prettyPrintRegionName(StringRef TopRegionName,
- StringRef Sep,
- bool IsReference,
- int IndirectionLevel,
- const MemRegion *ArgRegion,
- llvm::raw_svector_ostream &os,
- const PrintingPolicy &PP) {
- SmallVector<const MemRegion *, 5> Subregions;
+ RegionVector RegionSequence;
+
+ // Add the regions in the reverse order, then reverse the resulting array.
+ assert(RegionOfInterest->isSubRegionOf(MatchedRegion));
const MemRegion *R = RegionOfInterest;
- while (R != ArgRegion) {
- if (!(isa<FieldRegion>(R) || isa<CXXBaseObjectRegion>(R) ||
- isa<ObjCIvarRegion>(R)))
- return false; // Pattern-matching failed.
- Subregions.push_back(R);
+ while (R != MatchedRegion) {
+ RegionSequence.push_back(R);
R = cast<SubRegion>(R)->getSuperRegion();
}
- bool IndirectReference = !Subregions.empty();
+ std::reverse(RegionSequence.begin(), RegionSequence.end());
+ RegionSequence.append(FieldChain.begin(), FieldChain.end());
+
+ StringRef Sep;
+ for (const MemRegion *R : RegionSequence) {
+
+ // Just keep going up to the base region.
+ // Element regions may appear due to casts.
+ if (isa<CXXBaseObjectRegion>(R) || isa<CXXTempObjectRegion>(R))
+ continue;
+
+ if (Sep.empty())
+ Sep = prettyPrintFirstElement(FirstElement,
+ /*MoreItemsExpected=*/true,
+ IndirectionLevel, os);
- if (IndirectReference)
- IndirectionLevel--; // Due to "->" symbol.
+ os << Sep;
- if (IsReference)
- IndirectionLevel--; // Due to reference semantics.
+ // Can only reasonably pretty-print DeclRegions.
+ if (!isa<DeclRegion>(R))
+ return false;
- bool ShouldSurround = IndirectReference && IndirectionLevel > 0;
+ const auto *DR = cast<DeclRegion>(R);
+ Sep = DR->getValueType()->isAnyPointerType() ? "->" : ".";
+ DR->getDecl()->getDeclName().print(os, PP);
+ }
- if (ShouldSurround)
+ if (Sep.empty())
+ prettyPrintFirstElement(FirstElement,
+ /*MoreItemsExpected=*/false, IndirectionLevel,
+ os);
+ return true;
+ }
+
+ /// Print first item in the chain, return new separator.
+ StringRef prettyPrintFirstElement(StringRef FirstElement,
+ bool MoreItemsExpected,
+ int IndirectionLevel,
+ llvm::raw_svector_ostream &os) {
+ StringRef Out = ".";
+
+ if (IndirectionLevel > 0 && MoreItemsExpected) {
+ IndirectionLevel--;
+ Out = "->";
+ }
+
+ if (IndirectionLevel > 0 && MoreItemsExpected)
os << "(";
- for (int i = 0; i < IndirectionLevel; i++)
+
+ for (int i=0; i<IndirectionLevel; i++)
os << "*";
- os << TopRegionName;
- if (ShouldSurround)
+ os << FirstElement;
+
+ if (IndirectionLevel > 0 && MoreItemsExpected)
os << ")";
- for (auto I = Subregions.rbegin(), E = Subregions.rend(); I != E; ++I) {
- if (const auto *FR = dyn_cast<FieldRegion>(*I)) {
- os << Sep;
- FR->getDecl()->getDeclName().print(os, PP);
- Sep = ".";
- } else if (const auto *IR = dyn_cast<ObjCIvarRegion>(*I)) {
- os << "->";
- IR->getDecl()->getDeclName().print(os, PP);
- Sep = ".";
- } else if (isa<CXXBaseObjectRegion>(*I)) {
- continue; // Just keep going up to the base region.
- } else {
- llvm_unreachable("Previous check has missed an unexpected region");
- }
- }
- return true;
+ return Out;
}
};
@@ -579,7 +646,6 @@ public:
ValueAtDereference(V) {}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override {
if (WasModified)
@@ -590,10 +656,10 @@ public:
return nullptr;
const SourceManager &SMgr = BRC.getSourceManager();
- if (auto Loc = matchAssignment(N, BRC)) {
+ if (auto Loc = matchAssignment(N)) {
if (isFunctionMacroExpansion(*Loc, SMgr)) {
std::string MacroName = getMacroName(*Loc, BRC);
- SourceLocation BugLoc = BugPoint->getStmt()->getLocStart();
+ SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc();
if (!BugLoc.isMacroID() || getMacroName(BugLoc, BRC) != MacroName)
BR.markInvalid(getTag(), MacroName.c_str());
}
@@ -610,8 +676,8 @@ public:
bool EnableNullFPSuppression, BugReport &BR,
const SVal V) {
AnalyzerOptions &Options = N->getState()->getAnalysisManager().options;
- if (EnableNullFPSuppression && Options.shouldSuppressNullReturnPaths()
- && V.getAs<Loc>())
+ if (EnableNullFPSuppression &&
+ Options.ShouldSuppressNullReturnPaths && V.getAs<Loc>())
BR.addVisitor(llvm::make_unique<MacroNullReturnSuppressionVisitor>(
R->getAs<SubRegion>(), V));
}
@@ -628,8 +694,7 @@ public:
private:
/// \return Source location of right hand side of an assignment
/// into \c RegionOfInterest, empty optional if none found.
- Optional<SourceLocation> matchAssignment(const ExplodedNode *N,
- BugReporterContext &BRC) {
+ Optional<SourceLocation> matchAssignment(const ExplodedNode *N) {
const Stmt *S = PathDiagnosticLocation::getStmt(N);
ProgramStateRef State = N->getState();
auto *LCtx = N->getLocationContext();
@@ -641,12 +706,12 @@ private:
if (const Expr *RHS = VD->getInit())
if (RegionOfInterest->isSubRegionOf(
State->getLValue(VD, LCtx).getAsRegion()))
- return RHS->getLocStart();
+ return RHS->getBeginLoc();
} else if (const auto *BO = dyn_cast<BinaryOperator>(S)) {
const MemRegion *R = N->getSVal(BO->getLHS()).getAsRegion();
const Expr *RHS = BO->getRHS();
if (BO->isAssignmentOp() && RegionOfInterest->isSubRegionOf(R)) {
- return RHS->getLocStart();
+ return RHS->getBeginLoc();
}
}
return None;
@@ -670,10 +735,14 @@ class ReturnVisitor : public BugReporterVisitor {
bool EnableNullFPSuppression;
bool ShouldInvalidate = true;
+ AnalyzerOptions& Options;
public:
- ReturnVisitor(const StackFrameContext *Frame, bool Suppressed)
- : StackFrame(Frame), EnableNullFPSuppression(Suppressed) {}
+ ReturnVisitor(const StackFrameContext *Frame,
+ bool Suppressed,
+ AnalyzerOptions &Options)
+ : StackFrame(Frame), EnableNullFPSuppression(Suppressed),
+ Options(Options) {}
static void *getTag() {
static int Tag = 0;
@@ -701,10 +770,10 @@ public:
// First, find when we processed the statement.
do {
- if (Optional<CallExitEnd> CEE = Node->getLocationAs<CallExitEnd>())
+ if (auto CEE = Node->getLocationAs<CallExitEnd>())
if (CEE->getCalleeContext()->getCallSite() == S)
break;
- if (Optional<StmtPoint> SP = Node->getLocationAs<StmtPoint>())
+ if (auto SP = Node->getLocationAs<StmtPoint>())
if (SP->getStmt() == S)
break;
@@ -739,23 +808,19 @@ public:
AnalyzerOptions &Options = State->getAnalysisManager().options;
bool EnableNullFPSuppression = false;
- if (InEnableNullFPSuppression && Options.shouldSuppressNullReturnPaths())
+ if (InEnableNullFPSuppression &&
+ Options.ShouldSuppressNullReturnPaths)
if (Optional<Loc> RetLoc = RetVal.getAs<Loc>())
EnableNullFPSuppression = State->isNull(*RetLoc).isConstrainedTrue();
BR.markInteresting(CalleeContext);
BR.addVisitor(llvm::make_unique<ReturnVisitor>(CalleeContext,
- EnableNullFPSuppression));
- }
-
- /// Returns true if any counter-suppression heuristics are enabled for
- /// ReturnVisitor.
- static bool hasCounterSuppression(AnalyzerOptions &Options) {
- return Options.shouldAvoidSuppressingNullArgumentPaths();
+ EnableNullFPSuppression,
+ Options));
}
std::shared_ptr<PathDiagnosticPiece>
- visitNodeInitial(const ExplodedNode *N, const ExplodedNode *PrevN,
+ visitNodeInitial(const ExplodedNode *N,
BugReporterContext &BRC, BugReport &BR) {
// Only print a message at the interesting return statement.
if (N->getLocationContext() != StackFrame)
@@ -799,37 +864,40 @@ public:
RetE = RetE->IgnoreParenCasts();
- // If we can't prove the return value is 0, just mark it interesting, and
- // make sure to track it into any further inner functions.
- if (!State->isNull(V).isConstrainedTrue()) {
- BR.markInteresting(V);
- ReturnVisitor::addVisitorIfNecessary(N, RetE, BR,
- EnableNullFPSuppression);
- return nullptr;
- }
-
// If we're returning 0, we should track where that 0 came from.
- bugreporter::trackNullOrUndefValue(N, RetE, BR, /*IsArg*/ false,
- EnableNullFPSuppression);
+ bugreporter::trackExpressionValue(N, RetE, BR, EnableNullFPSuppression);
// Build an appropriate message based on the return value.
SmallString<64> Msg;
llvm::raw_svector_ostream Out(Msg);
- if (V.getAs<Loc>()) {
- // If we have counter-suppression enabled, make sure we keep visiting
- // future nodes. We want to emit a path note as well, in case
- // the report is resurrected as valid later on.
- AnalyzerOptions &Options = BRC.getAnalyzerOptions();
- if (EnableNullFPSuppression && hasCounterSuppression(Options))
- Mode = MaybeUnsuppress;
+ if (State->isNull(V).isConstrainedTrue()) {
+ if (V.getAs<Loc>()) {
+
+ // If we have counter-suppression enabled, make sure we keep visiting
+ // future nodes. We want to emit a path note as well, in case
+ // the report is resurrected as valid later on.
+ if (EnableNullFPSuppression &&
+ Options.ShouldAvoidSuppressingNullArgumentPaths)
+ Mode = MaybeUnsuppress;
+
+ if (RetE->getType()->isObjCObjectPointerType()) {
+ Out << "Returning nil";
+ } else {
+ Out << "Returning null pointer";
+ }
+ } else {
+ Out << "Returning zero";
+ }
- if (RetE->getType()->isObjCObjectPointerType())
- Out << "Returning nil";
- else
- Out << "Returning null pointer";
} else {
- Out << "Returning zero";
+ if (auto CI = V.getAs<nonloc::ConcreteInt>()) {
+ Out << "Returning the value " << CI->getValue();
+ } else if (V.getAs<Loc>()) {
+ Out << "Returning pointer";
+ } else {
+ Out << "Returning value";
+ }
}
if (LValue) {
@@ -855,11 +923,10 @@ public:
}
std::shared_ptr<PathDiagnosticPiece>
- visitNodeMaybeUnsuppress(const ExplodedNode *N, const ExplodedNode *PrevN,
+ visitNodeMaybeUnsuppress(const ExplodedNode *N,
BugReporterContext &BRC, BugReport &BR) {
#ifndef NDEBUG
- AnalyzerOptions &Options = BRC.getAnalyzerOptions();
- assert(hasCounterSuppression(Options));
+ assert(Options.ShouldAvoidSuppressingNullArgumentPaths);
#endif
// Are we at the entry node for this call?
@@ -893,8 +960,7 @@ public:
if (!State->isNull(*ArgV).isConstrainedTrue())
continue;
- if (bugreporter::trackNullOrUndefValue(N, ArgE, BR, /*IsArg=*/true,
- EnableNullFPSuppression))
+ if (bugreporter::trackExpressionValue(N, ArgE, BR, EnableNullFPSuppression))
ShouldInvalidate = false;
// If we /can't/ track the null pointer, we should err on the side of
@@ -906,14 +972,13 @@ public:
}
std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC,
BugReport &BR) override {
switch (Mode) {
case Initial:
- return visitNodeInitial(N, PrevN, BRC, BR);
+ return visitNodeInitial(N, BRC, BR);
case MaybeUnsuppress:
- return visitNodeMaybeUnsuppress(N, PrevN, BRC, BR);
+ return visitNodeMaybeUnsuppress(N, BRC, BR);
case Satisfied:
return nullptr;
}
@@ -921,7 +986,7 @@ public:
llvm_unreachable("Invalid visit mode!");
}
- void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *N,
+ void finalizeVisitor(BugReporterContext &, const ExplodedNode *,
BugReport &BR) override {
if (EnableNullFPSuppression && ShouldInvalidate)
BR.markInvalid(ReturnVisitor::getTag(), StackFrame);
@@ -1087,12 +1152,12 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os,
std::shared_ptr<PathDiagnosticPiece>
FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC, BugReport &BR) {
if (Satisfied)
return nullptr;
const ExplodedNode *StoreSite = nullptr;
+ const ExplodedNode *Pred = Succ->getFirstPred();
const Expr *InitE = nullptr;
bool IsParam = false;
@@ -1173,12 +1238,11 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ,
V.getAs<loc::ConcreteInt>() || V.getAs<nonloc::ConcreteInt>()) {
if (!IsParam)
InitE = InitE->IgnoreParenCasts();
- bugreporter::trackNullOrUndefValue(StoreSite, InitE, BR, IsParam,
- EnableNullFPSuppression);
- } else {
- ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(),
- BR, EnableNullFPSuppression);
+ bugreporter::trackExpressionValue(StoreSite, InitE, BR,
+ EnableNullFPSuppression);
}
+ ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(),
+ BR, EnableNullFPSuppression);
}
// Okay, we've found the binding. Emit an appropriate message.
@@ -1204,8 +1268,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ,
if (const auto *BDR =
dyn_cast_or_null<BlockDataRegion>(V.getAsRegion())) {
if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) {
- if (Optional<KnownSVal> KV =
- State->getSVal(OriginalR).getAs<KnownSVal>())
+ if (auto KV = State->getSVal(OriginalR).getAs<KnownSVal>())
BR.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>(
*KV, OriginalR, EnableNullFPSuppression));
}
@@ -1260,8 +1323,8 @@ bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const {
std::shared_ptr<PathDiagnosticPiece>
TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC, BugReport &BR) {
+ BugReporterContext &BRC, BugReport &) {
+ const ExplodedNode *PrevN = N->getFirstPred();
if (IsSatisfied)
return nullptr;
@@ -1316,7 +1379,7 @@ SuppressInlineDefensiveChecksVisitor(DefinedSVal Value, const ExplodedNode *N)
: V(Value) {
// Check if the visitor is disabled.
AnalyzerOptions &Options = N->getState()->getAnalysisManager().options;
- if (!Options.shouldSuppressInlinedDefensiveChecks())
+ if (!Options.ShouldSuppressInlinedDefensiveChecks)
IsSatisfied = true;
assert(N->getState()->isNull(V).isConstrainedTrue() &&
@@ -1336,9 +1399,9 @@ const char *SuppressInlineDefensiveChecksVisitor::getTag() {
std::shared_ptr<PathDiagnosticPiece>
SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
BugReporterContext &BRC,
BugReport &BR) {
+ const ExplodedNode *Pred = Succ->getFirstPred();
if (IsSatisfied)
return nullptr;
@@ -1379,7 +1442,7 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ,
CurTerminatorStmt = BE->getSrc()->getTerminator().getStmt();
} else if (auto SP = CurPoint.getAs<StmtPoint>()) {
const Stmt *CurStmt = SP->getStmt();
- if (!CurStmt->getLocStart().isMacroID())
+ if (!CurStmt->getBeginLoc().isMacroID())
return nullptr;
CFGStmtMap *Map = CurLC->getAnalysisDeclContext()->getCFGStmtMap();
@@ -1391,9 +1454,9 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ,
if (!CurTerminatorStmt)
return nullptr;
- SourceLocation TerminatorLoc = CurTerminatorStmt->getLocStart();
+ SourceLocation TerminatorLoc = CurTerminatorStmt->getBeginLoc();
if (TerminatorLoc.isMacroID()) {
- SourceLocation BugLoc = BugPoint->getStmt()->getLocStart();
+ SourceLocation BugLoc = BugPoint->getStmt()->getBeginLoc();
// Suppress reports unless we are in that same macro.
if (!BugLoc.isMacroID() ||
@@ -1427,11 +1490,13 @@ static const MemRegion *getLocationRegionIfReference(const Expr *E,
return nullptr;
}
+/// \return A subexpression of {@code Ex} which represents the
+/// expression-of-interest.
static const Expr *peelOffOuterExpr(const Expr *Ex,
const ExplodedNode *N) {
Ex = Ex->IgnoreParenCasts();
- if (const auto *EWC = dyn_cast<ExprWithCleanups>(Ex))
- return peelOffOuterExpr(EWC->getSubExpr(), N);
+ if (const auto *FE = dyn_cast<FullExpr>(Ex))
+ return peelOffOuterExpr(FE->getSubExpr(), N);
if (const auto *OVE = dyn_cast<OpaqueValueExpr>(Ex))
return peelOffOuterExpr(OVE->getSourceExpr(), N);
if (const auto *POE = dyn_cast<PseudoObjectExpr>(Ex)) {
@@ -1471,121 +1536,72 @@ static const Expr *peelOffOuterExpr(const Expr *Ex,
if (const Expr *SubEx = peelOffPointerArithmetic(BO))
return peelOffOuterExpr(SubEx, N);
- return Ex;
-}
+ if (auto *UO = dyn_cast<UnaryOperator>(Ex)) {
+ if (UO->getOpcode() == UO_LNot)
+ return peelOffOuterExpr(UO->getSubExpr(), N);
-/// Walk through nodes until we get one that matches the statement exactly.
-/// Alternately, if we hit a known lvalue for the statement, we know we've
-/// gone too far (though we can likely track the lvalue better anyway).
-static const ExplodedNode* findNodeForStatement(const ExplodedNode *N,
- const Stmt *S,
- const Expr *Inner) {
- do {
- const ProgramPoint &pp = N->getLocation();
- if (auto ps = pp.getAs<StmtPoint>()) {
- if (ps->getStmt() == S || ps->getStmt() == Inner)
- break;
- } else if (auto CEE = pp.getAs<CallExitEnd>()) {
- if (CEE->getCalleeContext()->getCallSite() == S ||
- CEE->getCalleeContext()->getCallSite() == Inner)
- break;
- }
- N = N->getFirstPred();
- } while (N);
- return N;
+ // FIXME: There's a hack in our Store implementation that always computes
+ // field offsets around null pointers as if they are always equal to 0.
+ // The idea here is to report accesses to fields as null dereferences
+ // even though the pointer value that's being dereferenced is actually
+ // the offset of the field rather than exactly 0.
+ // See the FIXME in StoreManager's getLValueFieldOrIvar() method.
+ // This code interacts heavily with this hack; otherwise the value
+ // would not be null at all for most fields, so we'd be unable to track it.
+ if (UO->getOpcode() == UO_AddrOf && UO->getSubExpr()->isLValue())
+ if (const Expr *DerefEx = bugreporter::getDerefExpr(UO->getSubExpr()))
+ return peelOffOuterExpr(DerefEx, N);
+ }
+
+ return Ex;
}
/// Find the ExplodedNode where the lvalue (the value of 'Ex')
/// was computed.
static const ExplodedNode* findNodeForExpression(const ExplodedNode *N,
- const Expr *Inner) {
+ const Expr *Inner) {
while (N) {
- if (auto P = N->getLocation().getAs<PostStmt>()) {
- if (P->getStmt() == Inner)
- break;
- }
+ if (PathDiagnosticLocation::getStmt(N) == Inner)
+ return N;
N = N->getFirstPred();
}
- assert(N && "Unable to find the lvalue node.");
return N;
}
-/// Performing operator `&' on an lvalue expression is essentially a no-op.
-/// Then, if we are taking addresses of fields or elements, these are also
-/// unlikely to matter.
-static const Expr* peelOfOuterAddrOf(const Expr* Ex) {
- Ex = Ex->IgnoreParenCasts();
-
- // FIXME: There's a hack in our Store implementation that always computes
- // field offsets around null pointers as if they are always equal to 0.
- // The idea here is to report accesses to fields as null dereferences
- // even though the pointer value that's being dereferenced is actually
- // the offset of the field rather than exactly 0.
- // See the FIXME in StoreManager's getLValueFieldOrIvar() method.
- // This code interacts heavily with this hack; otherwise the value
- // would not be null at all for most fields, so we'd be unable to track it.
- if (const auto *Op = dyn_cast<UnaryOperator>(Ex))
- if (Op->getOpcode() == UO_AddrOf && Op->getSubExpr()->isLValue())
- if (const Expr *DerefEx = bugreporter::getDerefExpr(Op->getSubExpr()))
- return DerefEx;
- return Ex;
-}
-
-bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N,
- const Stmt *S,
- BugReport &report, bool IsArg,
- bool EnableNullFPSuppression) {
- if (!S || !N)
+bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode,
+ const Expr *E, BugReport &report,
+ bool EnableNullFPSuppression) {
+ if (!E || !InputNode)
return false;
- if (const auto *Ex = dyn_cast<Expr>(S))
- S = peelOffOuterExpr(Ex, N);
-
- const Expr *Inner = nullptr;
- if (const auto *Ex = dyn_cast<Expr>(S)) {
- Ex = peelOfOuterAddrOf(Ex);
- Ex = Ex->IgnoreParenCasts();
-
- if (Ex && (ExplodedGraph::isInterestingLValueExpr(Ex)
- || CallEvent::isCallStmt(Ex)))
- Inner = Ex;
- }
-
- if (IsArg && !Inner) {
- assert(N->getLocation().getAs<CallEnter>() && "Tracking arg but not at call");
- } else {
- N = findNodeForStatement(N, S, Inner);
- if (!N)
- return false;
- }
+ const Expr *Inner = peelOffOuterExpr(E, InputNode);
+ const ExplodedNode *LVNode = findNodeForExpression(InputNode, Inner);
+ if (!LVNode)
+ return false;
- ProgramStateRef state = N->getState();
+ ProgramStateRef LVState = LVNode->getState();
// The message send could be nil due to the receiver being nil.
// At this point in the path, the receiver should be live since we are at the
// message send expr. If it is nil, start tracking it.
- if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(S, N))
- trackNullOrUndefValue(N, Receiver, report, /* IsArg=*/ false,
- EnableNullFPSuppression);
+ if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(Inner, LVNode))
+ trackExpressionValue(LVNode, Receiver, report, EnableNullFPSuppression);
// See if the expression we're interested refers to a variable.
// If so, we can track both its contents and constraints on its value.
- if (Inner && ExplodedGraph::isInterestingLValueExpr(Inner)) {
- const ExplodedNode *LVNode = findNodeForExpression(N, Inner);
- ProgramStateRef LVState = LVNode->getState();
+ if (ExplodedGraph::isInterestingLValueExpr(Inner)) {
SVal LVal = LVNode->getSVal(Inner);
- const MemRegion *RR = getLocationRegionIfReference(Inner, N);
+ const MemRegion *RR = getLocationRegionIfReference(Inner, LVNode);
bool LVIsNull = LVState->isNull(LVal).isConstrainedTrue();
// If this is a C++ reference to a null pointer, we are tracking the
// pointer. In addition, we should find the store at which the reference
// got initialized.
- if (RR && !LVIsNull) {
+ if (RR && !LVIsNull)
if (auto KV = LVal.getAs<KnownSVal>())
report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>(
*KV, RR, EnableNullFPSuppression));
- }
// In case of C++ references, we want to differentiate between a null
// reference and reference to null pointer.
@@ -1602,9 +1618,8 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N,
llvm::make_unique<NoStoreFuncVisitor>(cast<SubRegion>(R)));
MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary(
- N, R, EnableNullFPSuppression, report, V);
+ LVNode, R, EnableNullFPSuppression, report, V);
- report.markInteresting(R);
report.markInteresting(V);
report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(R));
@@ -1614,14 +1629,12 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N,
V.castAs<DefinedSVal>(), false));
// Add visitor, which will suppress inline defensive checks.
- if (auto DV = V.getAs<DefinedSVal>()) {
+ if (auto DV = V.getAs<DefinedSVal>())
if (!DV->isZeroConstant() && LVState->isNull(*DV).isConstrainedTrue() &&
- EnableNullFPSuppression) {
+ EnableNullFPSuppression)
report.addVisitor(
llvm::make_unique<SuppressInlineDefensiveChecksVisitor>(*DV,
- LVNode));
- }
- }
+ LVNode));
if (auto KV = V.getAs<KnownSVal>())
report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>(
@@ -1632,40 +1645,44 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N,
// If the expression is not an "lvalue expression", we can still
// track the constraints on its contents.
- SVal V = state->getSValAsScalarOrLoc(S, N->getLocationContext());
+ SVal V = LVState->getSValAsScalarOrLoc(Inner, LVNode->getLocationContext());
- // If the value came from an inlined function call, we should at least make
- // sure that function isn't pruned in our output.
- if (const auto *E = dyn_cast<Expr>(S))
- S = E->IgnoreParenCasts();
+ ReturnVisitor::addVisitorIfNecessary(
+ LVNode, Inner, report, EnableNullFPSuppression);
- ReturnVisitor::addVisitorIfNecessary(N, S, report, EnableNullFPSuppression);
-
- // Uncomment this to find cases where we aren't properly getting the
- // base value that was dereferenced.
- // assert(!V.isUnknownOrUndef());
// Is it a symbolic value?
if (auto L = V.getAs<loc::MemRegionVal>()) {
report.addVisitor(llvm::make_unique<UndefOrNullArgVisitor>(L->getRegion()));
+ // FIXME: this is a hack for fixing a later crash when attempting to
+ // dereference a void* pointer.
+ // We should not try to dereference pointers at all when we don't care
+ // what is written inside the pointer.
+ bool CanDereference = true;
+ if (const auto *SR = dyn_cast<SymbolicRegion>(L->getRegion()))
+ if (SR->getSymbol()->getType()->getPointeeType()->isVoidType())
+ CanDereference = false;
+
// At this point we are dealing with the region's LValue.
// However, if the rvalue is a symbolic region, we should track it as well.
// Try to use the correct type when looking up the value.
SVal RVal;
- if (const auto *E = dyn_cast<Expr>(S))
- RVal = state->getRawSVal(L.getValue(), E->getType());
- else
- RVal = state->getSVal(L->getRegion());
+ if (ExplodedGraph::isInterestingLValueExpr(Inner)) {
+ RVal = LVState->getRawSVal(L.getValue(), Inner->getType());
+ } else if (CanDereference) {
+ RVal = LVState->getSVal(L->getRegion());
+ }
- if (auto KV = RVal.getAs<KnownSVal>())
- report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>(
+ if (CanDereference)
+ if (auto KV = RVal.getAs<KnownSVal>())
+ report.addVisitor(llvm::make_unique<FindLastStoreBRVisitor>(
*KV, L->getRegion(), EnableNullFPSuppression));
const MemRegion *RegionRVal = RVal.getAsRegion();
if (RegionRVal && isa<SymbolicRegion>(RegionRVal)) {
report.markInteresting(RegionRVal);
report.addVisitor(llvm::make_unique<TrackConstraintBRVisitor>(
- loc::MemRegionVal(RegionRVal), false));
+ loc::MemRegionVal(RegionRVal), /*assumption=*/false));
}
}
return true;
@@ -1687,7 +1704,6 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S,
std::shared_ptr<PathDiagnosticPiece>
NilReceiverBRVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC, BugReport &BR) {
Optional<PreStmt> P = N->getLocationAs<PreStmt>();
if (!P)
@@ -1714,8 +1730,8 @@ NilReceiverBRVisitor::VisitNode(const ExplodedNode *N,
// The receiver was nil, and hence the method was skipped.
// Register a BugReporterVisitor to issue a message telling us how
// the receiver was null.
- bugreporter::trackNullOrUndefValue(N, Receiver, BR, /*IsArg*/ false,
- /*EnableNullFPSuppression*/ false);
+ bugreporter::trackExpressionValue(N, Receiver, BR,
+ /*EnableNullFPSuppression*/ false);
// Issue a message saying that the method was skipped.
PathDiagnosticLocation L(Receiver, BRC.getSourceManager(),
N->getLocationContext());
@@ -1768,9 +1784,9 @@ const char *ConditionBRVisitor::getTag() {
}
std::shared_ptr<PathDiagnosticPiece>
-ConditionBRVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *Prev,
+ConditionBRVisitor::VisitNode(const ExplodedNode *N,
BugReporterContext &BRC, BugReport &BR) {
- auto piece = VisitNodeImpl(N, Prev, BRC, BR);
+ auto piece = VisitNodeImpl(N, BRC, BR);
if (piece) {
piece->setTag(getTag());
if (auto *ev = dyn_cast<PathDiagnosticEventPiece>(piece.get()))
@@ -1781,11 +1797,10 @@ ConditionBRVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *Prev,
std::shared_ptr<PathDiagnosticPiece>
ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N,
- const ExplodedNode *Prev,
BugReporterContext &BRC, BugReport &BR) {
ProgramPoint progPoint = N->getLocation();
ProgramStateRef CurrentState = N->getState();
- ProgramStateRef PrevState = Prev->getState();
+ ProgramStateRef PrevState = N->getFirstPred()->getState();
// Compare the GDMs of the state, because that is where constraints
// are managed. Note that ensure that we only look at nodes that
@@ -1936,8 +1951,8 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex,
// Use heuristics to determine if Ex is a macro expending to a literal and
// if so, use the macro's name.
- SourceLocation LocStart = Ex->getLocStart();
- SourceLocation LocEnd = Ex->getLocEnd();
+ SourceLocation LocStart = Ex->getBeginLoc();
+ SourceLocation LocEnd = Ex->getEndLoc();
if (LocStart.isMacroID() && LocEnd.isMacroID() &&
(isa<GNUNullExpr>(Ex) ||
isa<ObjCBoolLiteralExpr>(Ex) ||
@@ -1951,10 +1966,10 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex,
bool beginAndEndAreTheSameMacro = StartName.equals(EndName);
bool partOfParentMacro = false;
- if (ParentEx->getLocStart().isMacroID()) {
+ if (ParentEx->getBeginLoc().isMacroID()) {
StringRef PName = Lexer::getImmediateMacroNameForDiagnostics(
- ParentEx->getLocStart(), BRC.getSourceManager(),
- BRC.getASTContext().getLangOpts());
+ ParentEx->getBeginLoc(), BRC.getSourceManager(),
+ BRC.getASTContext().getLangOpts());
partOfParentMacro = PName.equals(StartName);
}
@@ -2205,7 +2220,7 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor(
// the user's fault, we currently don't report them very well, and
// Note that this will not help for any other data structure libraries, like
// TR1, Boost, or llvm/ADT.
- if (Options.shouldSuppressFromCXXStandardLibrary()) {
+ if (Options.ShouldSuppressFromCXXStandardLibrary) {
BR.markInvalid(getTag(), nullptr);
return;
} else {
@@ -2277,7 +2292,6 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor(
std::shared_ptr<PathDiagnosticPiece>
UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
BugReporterContext &BRC, BugReport &BR) {
ProgramStateRef State = N->getState();
ProgramPoint ProgLoc = N->getLocation();
@@ -2328,8 +2342,7 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N,
std::shared_ptr<PathDiagnosticPiece>
CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ,
- const ExplodedNode *Pred,
- BugReporterContext &BRC, BugReport &BR) {
+ BugReporterContext &BRC, BugReport &) {
if (Satisfied)
return nullptr;
@@ -2380,11 +2393,11 @@ CXXSelfAssignmentBRVisitor::VisitNode(const ExplodedNode *Succ,
}
std::shared_ptr<PathDiagnosticPiece>
-TaintBugVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN,
- BugReporterContext &BRC, BugReport &BR) {
+TaintBugVisitor::VisitNode(const ExplodedNode *N,
+ BugReporterContext &BRC, BugReport &) {
// Find the ExplodedNode where the taint was first introduced
- if (!N->getState()->isTainted(V) || PrevN->getState()->isTainted(V))
+ if (!N->getState()->isTainted(V) || N->getFirstPred()->getState()->isTainted(V))
return nullptr;
const Stmt *S = PathDiagnosticLocation::getStmt(N);
@@ -2406,36 +2419,43 @@ FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor()
void FalsePositiveRefutationBRVisitor::finalizeVisitor(
BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) {
// Collect new constraints
- VisitNode(EndPathNode, nullptr, BRC, BR);
+ VisitNode(EndPathNode, BRC, BR);
// Create a refutation manager
- std::unique_ptr<SMTSolver> RefutationSolver = CreateZ3Solver();
+ SMTSolverRef RefutationSolver = CreateZ3Solver();
ASTContext &Ctx = BRC.getASTContext();
// Add constraints to the solver
for (const auto &I : Constraints) {
- SymbolRef Sym = I.first;
+ const SymbolRef Sym = I.first;
+ auto RangeIt = I.second.begin();
- SMTExprRef Constraints = RefutationSolver->fromBoolean(false);
- for (const auto &Range : I.second) {
+ SMTExprRef Constraints = SMTConv::getRangeExpr(
+ RefutationSolver, Ctx, Sym, RangeIt->From(), RangeIt->To(),
+ /*InRange=*/true);
+ while ((++RangeIt) != I.second.end()) {
Constraints = RefutationSolver->mkOr(
- Constraints,
- RefutationSolver->getRangeExpr(Ctx, Sym, Range.From(), Range.To(),
- /*InRange=*/true));
+ Constraints, SMTConv::getRangeExpr(RefutationSolver, Ctx, Sym,
+ RangeIt->From(), RangeIt->To(),
+ /*InRange=*/true));
}
+
RefutationSolver->addConstraint(Constraints);
}
// And check for satisfiability
- if (RefutationSolver->check().isConstrainedFalse())
+ Optional<bool> isSat = RefutationSolver->check();
+ if (!isSat.hasValue())
+ return;
+
+ if (!isSat.getValue())
BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext());
}
std::shared_ptr<PathDiagnosticPiece>
FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N,
- const ExplodedNode *PrevN,
- BugReporterContext &BRC,
- BugReport &BR) {
+ BugReporterContext &,
+ BugReport &) {
// Collect new constraints
const ConstraintRangeTy &NewCs = N->getState()->get<ConstraintRange>();
ConstraintRangeTy::Factory &CF =
diff --git a/lib/StaticAnalyzer/Core/CMakeLists.txt b/lib/StaticAnalyzer/Core/CMakeLists.txt
index de994b598e59..167f78af6289 100644
--- a/lib/StaticAnalyzer/Core/CMakeLists.txt
+++ b/lib/StaticAnalyzer/Core/CMakeLists.txt
@@ -20,7 +20,6 @@ add_clang_library(clangStaticAnalyzerCore
CheckerContext.cpp
CheckerHelpers.cpp
CheckerManager.cpp
- CheckerRegistry.cpp
CommonBugCategories.cpp
ConstraintManager.cpp
CoreEngine.cpp
@@ -44,14 +43,16 @@ add_clang_library(clangStaticAnalyzerCore
RangeConstraintManager.cpp
RangedConstraintManager.cpp
RegionStore.cpp
- SValBuilder.cpp
- SVals.cpp
+ RetainSummaryManager.cpp
+ SarifDiagnostics.cpp
SimpleConstraintManager.cpp
SimpleSValBuilder.cpp
- SMTConstraintManager.cpp
Store.cpp
SubEngine.cpp
+ SValBuilder.cpp
+ SVals.cpp
SymbolManager.cpp
+ TaintManager.cpp
WorkList.cpp
Z3ConstraintManager.cpp
diff --git a/lib/StaticAnalyzer/Core/CallEvent.cpp b/lib/StaticAnalyzer/Core/CallEvent.cpp
index fe9260e32dd8..0e7f31502e81 100644
--- a/lib/StaticAnalyzer/Core/CallEvent.cpp
+++ b/lib/StaticAnalyzer/Core/CallEvent.cpp
@@ -169,18 +169,27 @@ bool CallEvent::isGlobalCFunction(StringRef FunctionName) const {
AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const {
const Decl *D = getDecl();
-
- // If the callee is completely unknown, we cannot construct the stack frame.
if (!D)
return nullptr;
- // FIXME: Skip virtual functions for now. There's no easy procedure to foresee
- // the exact decl that should be used, especially when it's not a definition.
- if (const Decl *RD = getRuntimeDefinition().getDecl())
- if (RD != D)
- return nullptr;
+ // TODO: For now we skip functions without definitions, even if we have
+ // our own getDecl(), because it's hard to find out which re-declaration
+ // is going to be used, and usually clients don't really care about this
+ // situation because there's a loss of precision anyway because we cannot
+ // inline the call.
+ RuntimeDefinition RD = getRuntimeDefinition();
+ if (!RD.getDecl())
+ return nullptr;
+
+ AnalysisDeclContext *ADC =
+ LCtx->getAnalysisDeclContext()->getManager()->getContext(D);
+
+ // TODO: For now we skip virtual functions, because this also rises
+ // the problem of which decl to use, but now it's across different classes.
+ if (RD.mayHaveOtherDefinitions() || RD.getDecl() != ADC->getDecl())
+ return nullptr;
- return LCtx->getAnalysisDeclContext()->getManager()->getContext(D);
+ return ADC;
}
const StackFrameContext *CallEvent::getCalleeStackFrame() const {
@@ -218,7 +227,24 @@ const VarRegion *CallEvent::getParameterLocation(unsigned Index) const {
if (!SFC)
return nullptr;
- const ParmVarDecl *PVD = parameters()[Index];
+ // Retrieve parameters of the definition, which are different from
+ // CallEvent's parameters() because getDecl() isn't necessarily
+ // the definition. SFC contains the definition that would be used
+ // during analysis.
+ const Decl *D = SFC->getDecl();
+
+ // TODO: Refactor into a virtual method of CallEvent, like parameters().
+ const ParmVarDecl *PVD = nullptr;
+ if (const auto *FD = dyn_cast<FunctionDecl>(D))
+ PVD = FD->parameters()[Index];
+ else if (const auto *BD = dyn_cast<BlockDecl>(D))
+ PVD = BD->parameters()[Index];
+ else if (const auto *MD = dyn_cast<ObjCMethodDecl>(D))
+ PVD = MD->parameters()[Index];
+ else if (const auto *CD = dyn_cast<CXXConstructorDecl>(D))
+ PVD = CD->parameters()[Index];
+ assert(PVD && "Unexpected Decl kind!");
+
const VarRegion *VR =
State->getStateManager().getRegionManager().getVarRegion(PVD, SFC);
@@ -285,6 +311,20 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount,
// TODO: Factor this out + handle the lower level const pointers.
ValuesToInvalidate.push_back(getArgSVal(Idx));
+
+ // If a function accepts an object by argument (which would of course be a
+ // temporary that isn't lifetime-extended), invalidate the object itself,
+ // not only other objects reachable from it. This is necessary because the
+ // destructor has access to the temporary object after the call.
+ // TODO: Support placement arguments once we start
+ // constructing them directly.
+ // TODO: This is unnecessary when there's no destructor, but that's
+ // currently hard to figure out.
+ if (getKind() != CE_CXXAllocator)
+ if (isArgumentConstructedDirectly(Idx))
+ if (auto AdjIdx = getAdjustedParameterIndex(Idx))
+ if (const VarRegion *VR = getParameterLocation(*AdjIdx))
+ ValuesToInvalidate.push_back(loc::MemRegionVal(VR));
}
// Invalidate designated regions using the batch invalidation API.
@@ -319,11 +359,41 @@ bool CallEvent::isCalled(const CallDescription &CD) const {
return false;
if (!CD.IsLookupDone) {
CD.IsLookupDone = true;
- CD.II = &getState()->getStateManager().getContext().Idents.get(CD.FuncName);
+ CD.II = &getState()->getStateManager().getContext().Idents.get(
+ CD.getFunctionName());
}
const IdentifierInfo *II = getCalleeIdentifier();
if (!II || II != CD.II)
return false;
+
+ const Decl *D = getDecl();
+ // If CallDescription provides prefix names, use them to improve matching
+ // accuracy.
+ if (CD.QualifiedName.size() > 1 && D) {
+ const DeclContext *Ctx = D->getDeclContext();
+ // See if we'll be able to match them all.
+ size_t NumUnmatched = CD.QualifiedName.size() - 1;
+ for (; Ctx && isa<NamedDecl>(Ctx); Ctx = Ctx->getParent()) {
+ if (NumUnmatched == 0)
+ break;
+
+ if (const auto *ND = dyn_cast<NamespaceDecl>(Ctx)) {
+ if (ND->getName() == CD.QualifiedName[NumUnmatched - 1])
+ --NumUnmatched;
+ continue;
+ }
+
+ if (const auto *RD = dyn_cast<RecordDecl>(Ctx)) {
+ if (RD->getName() == CD.QualifiedName[NumUnmatched - 1])
+ --NumUnmatched;
+ continue;
+ }
+ }
+
+ if (NumUnmatched > 0)
+ return false;
+ }
+
return (CD.RequiredArgs == CallDescription::NoArgRequirement ||
CD.RequiredArgs == getNumArgs());
}
@@ -433,6 +503,14 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx,
const ParmVarDecl *ParamDecl = *I;
assert(ParamDecl && "Formal parameter has no decl?");
+ // TODO: Support allocator calls.
+ if (Call.getKind() != CE_CXXAllocator)
+ if (Call.isArgumentConstructedDirectly(Idx))
+ continue;
+
+ // TODO: Allocators should receive the correct size and possibly alignment,
+ // determined in compile-time but not represented as arg-expressions,
+ // which makes getArgSVal() fail and return UnknownVal.
SVal ArgVal = Call.getArgSVal(Idx);
if (!ArgVal.isUnknown()) {
Loc ParamLoc = SVB.makeLoc(MRMgr.getVarRegion(ParamDecl, CalleeCtx));
@@ -472,17 +550,18 @@ RuntimeDefinition AnyFunctionCall::getRuntimeDefinition() const {
return RuntimeDefinition(Decl);
}
- SubEngine *Engine = getState()->getStateManager().getOwningEngine();
- AnalyzerOptions &Opts = Engine->getAnalysisManager().options;
+ SubEngine &Engine = getState()->getStateManager().getOwningEngine();
+ AnalyzerOptions &Opts = Engine.getAnalysisManager().options;
// Try to get CTU definition only if CTUDir is provided.
- if (!Opts.naiveCTUEnabled())
+ if (!Opts.IsNaiveCTUEnabled)
return {};
cross_tu::CrossTranslationUnitContext &CTUCtx =
- *Engine->getCrossTranslationUnitContext();
+ *Engine.getCrossTranslationUnitContext();
llvm::Expected<const FunctionDecl *> CTUDeclOrError =
- CTUCtx.getCrossTUDefinition(FD, Opts.getCTUDir(), Opts.getCTUIndexName());
+ CTUCtx.getCrossTUDefinition(FD, Opts.CTUDir, Opts.CTUIndexName,
+ Opts.DisplayCTUProgress);
if (!CTUDeclOrError) {
handleAllErrors(CTUDeclOrError.takeError(),
@@ -758,7 +837,7 @@ const BlockDataRegion *BlockCall::getBlockRegion() const {
ArrayRef<ParmVarDecl*> BlockCall::parameters() const {
const BlockDecl *D = getDecl();
if (!D)
- return nullptr;
+ return None;
return D->parameters();
}
@@ -1008,7 +1087,7 @@ bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl,
Selector Sel) const {
assert(IDecl);
AnalysisManager &AMgr =
- getState()->getStateManager().getOwningEngine()->getAnalysisManager();
+ getState()->getStateManager().getOwningEngine().getAnalysisManager();
// If the class interface is declared inside the main file, assume it is not
// subcassed.
// TODO: It could actually be subclassed if the subclass is private as well.
@@ -1290,28 +1369,20 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx,
const Stmt *CallSite = CalleeCtx->getCallSite();
if (CallSite) {
- if (const CallExpr *CE = dyn_cast<CallExpr>(CallSite))
- return getSimpleCall(CE, State, CallerCtx);
-
- switch (CallSite->getStmtClass()) {
- case Stmt::CXXConstructExprClass:
- case Stmt::CXXTemporaryObjectExprClass: {
- SValBuilder &SVB = State->getStateManager().getSValBuilder();
- const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl());
- Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx);
- SVal ThisVal = State->getSVal(ThisPtr);
-
- return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite),
- ThisVal.getAsRegion(), State, CallerCtx);
- }
- case Stmt::CXXNewExprClass:
- return getCXXAllocatorCall(cast<CXXNewExpr>(CallSite), State, CallerCtx);
- case Stmt::ObjCMessageExprClass:
- return getObjCMethodCall(cast<ObjCMessageExpr>(CallSite),
- State, CallerCtx);
- default:
- llvm_unreachable("This is not an inlineable statement.");
- }
+ if (CallEventRef<> Out = getCall(CallSite, State, CallerCtx))
+ return Out;
+
+ // All other cases are handled by getCall.
+ assert(isa<CXXConstructExpr>(CallSite) &&
+ "This is not an inlineable statement");
+
+ SValBuilder &SVB = State->getStateManager().getSValBuilder();
+ const auto *Ctor = cast<CXXMethodDecl>(CalleeCtx->getDecl());
+ Loc ThisPtr = SVB.getCXXThis(Ctor, CalleeCtx);
+ SVal ThisVal = State->getSVal(ThisPtr);
+
+ return getCXXConstructorCall(cast<CXXConstructExpr>(CallSite),
+ ThisVal.getAsRegion(), State, CallerCtx);
}
// Fall back to the CFG. The only thing we haven't handled yet is
@@ -1338,3 +1409,16 @@ CallEventManager::getCaller(const StackFrameContext *CalleeCtx,
E.getAs<CFGBaseDtor>().hasValue(), State,
CallerCtx);
}
+
+CallEventRef<> CallEventManager::getCall(const Stmt *S, ProgramStateRef State,
+ const LocationContext *LC) {
+ if (const auto *CE = dyn_cast<CallExpr>(S)) {
+ return getSimpleCall(CE, State, LC);
+ } else if (const auto *NE = dyn_cast<CXXNewExpr>(S)) {
+ return getCXXAllocatorCall(NE, State, LC);
+ } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(S)) {
+ return getObjCMethodCall(ME, State, LC);
+ } else {
+ return nullptr;
+ }
+}
diff --git a/lib/StaticAnalyzer/Core/Checker.cpp b/lib/StaticAnalyzer/Core/Checker.cpp
index b422a8871983..72bfd84b40a3 100644
--- a/lib/StaticAnalyzer/Core/Checker.cpp
+++ b/lib/StaticAnalyzer/Core/Checker.cpp
@@ -17,6 +17,8 @@
using namespace clang;
using namespace ento;
+int ImplicitNullDerefEvent::Tag;
+
StringRef CheckerBase::getTagDescription() const {
return getCheckName().getName();
}
diff --git a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp
index b9facffcc8b5..e73a22ae3981 100644
--- a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp
+++ b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp
@@ -21,10 +21,10 @@ namespace ento {
// Recursively find any substatements containing macros
bool containsMacro(const Stmt *S) {
- if (S->getLocStart().isMacroID())
+ if (S->getBeginLoc().isMacroID())
return true;
- if (S->getLocEnd().isMacroID())
+ if (S->getEndLoc().isMacroID())
return true;
for (const Stmt *Child : S->children())
@@ -103,9 +103,9 @@ Nullability getNullabilityAnnotation(QualType Type) {
const auto *AttrType = Type->getAs<AttributedType>();
if (!AttrType)
return Nullability::Unspecified;
- if (AttrType->getAttrKind() == AttributedType::attr_nullable)
+ if (AttrType->getAttrKind() == attr::TypeNullable)
return Nullability::Nullable;
- else if (AttrType->getAttrKind() == AttributedType::attr_nonnull)
+ else if (AttrType->getAttrKind() == attr::TypeNonNull)
return Nullability::Nonnull;
return Nullability::Unspecified;
}
diff --git a/lib/StaticAnalyzer/Core/CheckerManager.cpp b/lib/StaticAnalyzer/Core/CheckerManager.cpp
index 712872a15d8a..688c47e984cc 100644
--- a/lib/StaticAnalyzer/Core/CheckerManager.cpp
+++ b/lib/StaticAnalyzer/Core/CheckerManager.cpp
@@ -441,14 +441,13 @@ void CheckerManager::runCheckersForEndFunction(NodeBuilderContext &BC,
ExplodedNode *Pred,
ExprEngine &Eng,
const ReturnStmt *RS) {
- // We define the builder outside of the loop bacause if at least one checkers
- // creates a sucsessor for Pred, we do not need to generate an
+ // We define the builder outside of the loop because if at least one checker
+ // creates a successor for Pred, we do not need to generate an
// autotransition for it.
NodeBuilder Bldr(Pred, Dst, BC);
for (const auto checkFn : EndFunctionCheckers) {
- const ProgramPoint &L = BlockEntrance(BC.Block,
- Pred->getLocationContext(),
- checkFn.Checker);
+ const ProgramPoint &L =
+ FunctionExitPoint(RS, Pred->getLocationContext(), checkFn.Checker);
CheckerContext C(Bldr, Eng, Pred, L);
checkFn(RS, C);
}
diff --git a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp
deleted file mode 100644
index 645845ec2181..000000000000
--- a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp
+++ /dev/null
@@ -1,190 +0,0 @@
-//===- CheckerRegistry.cpp - Maintains all available checkers -------------===//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/StaticAnalyzer/Core/CheckerRegistry.h"
-#include "clang/Basic/Diagnostic.h"
-#include "clang/Basic/LLVM.h"
-#include "clang/Frontend/FrontendDiagnostic.h"
-#include "clang/StaticAnalyzer/Core/CheckerManager.h"
-#include "clang/StaticAnalyzer/Core/CheckerOptInfo.h"
-#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
-#include "llvm/ADT/STLExtras.h"
-#include "llvm/ADT/SetVector.h"
-#include "llvm/ADT/StringMap.h"
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/raw_ostream.h"
-#include <algorithm>
-#include <cstddef>
-#include <tuple>
-
-using namespace clang;
-using namespace ento;
-
-static const char PackageSeparator = '.';
-
-using CheckerInfoSet = llvm::SetVector<const CheckerRegistry::CheckerInfo *>;
-
-static bool checkerNameLT(const CheckerRegistry::CheckerInfo &a,
- const CheckerRegistry::CheckerInfo &b) {
- return a.FullName < b.FullName;
-}
-
-static bool isInPackage(const CheckerRegistry::CheckerInfo &checker,
- StringRef packageName) {
- // Does the checker's full name have the package as a prefix?
- if (!checker.FullName.startswith(packageName))
- return false;
-
- // Is the package actually just the name of a specific checker?
- if (checker.FullName.size() == packageName.size())
- return true;
-
- // Is the checker in the package (or a subpackage)?
- if (checker.FullName[packageName.size()] == PackageSeparator)
- return true;
-
- return false;
-}
-
-static void collectCheckers(const CheckerRegistry::CheckerInfoList &checkers,
- const llvm::StringMap<size_t> &packageSizes,
- CheckerOptInfo &opt, CheckerInfoSet &collected) {
- // Use a binary search to find the possible start of the package.
- CheckerRegistry::CheckerInfo packageInfo(nullptr, opt.getName(), "");
- auto end = checkers.cend();
- auto i = std::lower_bound(checkers.cbegin(), end, packageInfo, checkerNameLT);
-
- // If we didn't even find a possible package, give up.
- if (i == end)
- return;
-
- // If what we found doesn't actually start the package, give up.
- if (!isInPackage(*i, opt.getName()))
- return;
-
- // There is at least one checker in the package; claim the option.
- opt.claim();
-
- // See how large the package is.
- // If the package doesn't exist, assume the option refers to a single checker.
- size_t size = 1;
- llvm::StringMap<size_t>::const_iterator packageSize =
- packageSizes.find(opt.getName());
- if (packageSize != packageSizes.end())
- size = packageSize->getValue();
-
- // Step through all the checkers in the package.
- for (auto checkEnd = i+size; i != checkEnd; ++i)
- if (opt.isEnabled())
- collected.insert(&*i);
- else
- collected.remove(&*i);
-}
-
-void CheckerRegistry::addChecker(InitializationFunction fn, StringRef name,
- StringRef desc) {
- Checkers.push_back(CheckerInfo(fn, name, desc));
-
- // Record the presence of the checker in its packages.
- StringRef packageName, leafName;
- std::tie(packageName, leafName) = name.rsplit(PackageSeparator);
- while (!leafName.empty()) {
- Packages[packageName] += 1;
- std::tie(packageName, leafName) = packageName.rsplit(PackageSeparator);
- }
-}
-
-void CheckerRegistry::initializeManager(CheckerManager &checkerMgr,
- SmallVectorImpl<CheckerOptInfo> &opts) const {
- // Sort checkers for efficient collection.
- llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT);
-
- // Collect checkers enabled by the options.
- CheckerInfoSet enabledCheckers;
- for (auto &i : opts)
- collectCheckers(Checkers, Packages, i, enabledCheckers);
-
- // Initialize the CheckerManager with all enabled checkers.
- for (const auto *i :enabledCheckers) {
- checkerMgr.setCurrentCheckName(CheckName(i->FullName));
- i->Initialize(checkerMgr);
- }
-}
-
-void CheckerRegistry::validateCheckerOptions(const AnalyzerOptions &opts,
- DiagnosticsEngine &diags) const {
- for (const auto &config : opts.Config) {
- size_t pos = config.getKey().find(':');
- if (pos == StringRef::npos)
- continue;
-
- bool hasChecker = false;
- StringRef checkerName = config.getKey().substr(0, pos);
- for (const auto &checker : Checkers) {
- if (checker.FullName.startswith(checkerName) &&
- (checker.FullName.size() == pos || checker.FullName[pos] == '.')) {
- hasChecker = true;
- break;
- }
- }
- if (!hasChecker)
- diags.Report(diag::err_unknown_analyzer_checker) << checkerName;
- }
-}
-
-void CheckerRegistry::printHelp(raw_ostream &out,
- size_t maxNameChars) const {
- // FIXME: Alphabetical sort puts 'experimental' in the middle.
- // Would it be better to name it '~experimental' or something else
- // that's ASCIIbetically last?
- llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT);
-
- // FIXME: Print available packages.
-
- out << "CHECKERS:\n";
-
- // Find the maximum option length.
- size_t optionFieldWidth = 0;
- for (const auto &i : Checkers) {
- // Limit the amount of padding we are willing to give up for alignment.
- // Package.Name Description [Hidden]
- size_t nameLength = i.FullName.size();
- if (nameLength <= maxNameChars)
- optionFieldWidth = std::max(optionFieldWidth, nameLength);
- }
-
- const size_t initialPad = 2;
- for (const auto &i : Checkers) {
- out.indent(initialPad) << i.FullName;
-
- int pad = optionFieldWidth - i.FullName.size();
-
- // Break on long option names.
- if (pad < 0) {
- out << '\n';
- pad = optionFieldWidth + initialPad;
- }
- out.indent(pad + 2) << i.Desc;
-
- out << '\n';
- }
-}
-
-void CheckerRegistry::printList(
- raw_ostream &out, SmallVectorImpl<CheckerOptInfo> &opts) const {
- llvm::sort(Checkers.begin(), Checkers.end(), checkerNameLT);
-
- // Collect checkers enabled by the options.
- CheckerInfoSet enabledCheckers;
- for (auto &i : opts)
- collectCheckers(Checkers, Packages, i, enabledCheckers);
-
- for (const auto *i : enabledCheckers)
- out << i->FullName << '\n';
-}
diff --git a/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/lib/StaticAnalyzer/Core/CommonBugCategories.cpp
index 421dfa48c97b..cdae3ef0116a 100644
--- a/lib/StaticAnalyzer/Core/CommonBugCategories.cpp
+++ b/lib/StaticAnalyzer/Core/CommonBugCategories.cpp
@@ -14,8 +14,8 @@ namespace clang { namespace ento { namespace categories {
const char * const CoreFoundationObjectiveC = "Core Foundation/Objective-C";
const char * const LogicError = "Logic error";
-const char * const MemoryCoreFoundationObjectiveC =
- "Memory (Core Foundation/Objective-C)";
+const char * const MemoryRefCount =
+ "Memory (Core Foundation/Objective-C/OSObject)";
const char * const MemoryError = "Memory error";
const char * const UnixAPI = "Unix API";
}}}
diff --git a/lib/StaticAnalyzer/Core/CoreEngine.cpp b/lib/StaticAnalyzer/Core/CoreEngine.cpp
index c17b6aae37e2..196854cb09da 100644
--- a/lib/StaticAnalyzer/Core/CoreEngine.cpp
+++ b/lib/StaticAnalyzer/Core/CoreEngine.cpp
@@ -53,26 +53,28 @@ STATISTIC(NumPathsExplored,
// Core analysis engine.
//===----------------------------------------------------------------------===//
-static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts) {
+static std::unique_ptr<WorkList> generateWorkList(AnalyzerOptions &Opts,
+ SubEngine &subengine) {
switch (Opts.getExplorationStrategy()) {
- case AnalyzerOptions::ExplorationStrategyKind::DFS:
+ case ExplorationStrategyKind::DFS:
return WorkList::makeDFS();
- case AnalyzerOptions::ExplorationStrategyKind::BFS:
+ case ExplorationStrategyKind::BFS:
return WorkList::makeBFS();
- case AnalyzerOptions::ExplorationStrategyKind::BFSBlockDFSContents:
+ case ExplorationStrategyKind::BFSBlockDFSContents:
return WorkList::makeBFSBlockDFSContents();
- case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirst:
+ case ExplorationStrategyKind::UnexploredFirst:
return WorkList::makeUnexploredFirst();
- case AnalyzerOptions::ExplorationStrategyKind::UnexploredFirstQueue:
+ case ExplorationStrategyKind::UnexploredFirstQueue:
return WorkList::makeUnexploredFirstPriorityQueue();
- default:
- llvm_unreachable("Unexpected case");
+ case ExplorationStrategyKind::UnexploredFirstLocationQueue:
+ return WorkList::makeUnexploredFirstPriorityLocationQueue();
}
+ llvm_unreachable("Unknown AnalyzerOptions::ExplorationStrategyKind");
}
CoreEngine::CoreEngine(SubEngine &subengine, FunctionSummariesTy *FS,
AnalyzerOptions &Opts)
- : SubEng(subengine), WList(generateWorkList(Opts)),
+ : SubEng(subengine), WList(generateWorkList(Opts, subengine)),
BCounterFactory(G.getAllocator()), FunctionSummaries(FS) {}
/// ExecuteWorkList - Run the worklist algorithm for a maximum number of steps.
@@ -146,7 +148,7 @@ bool CoreEngine::ExecuteWorkList(const LocationContext *L, unsigned Steps,
dispatchWorkItem(Node, Node->getLocation(), WU);
}
- SubEng.processEndWorklist(hasWorkRemaining());
+ SubEng.processEndWorklist();
return WList->hasWork();
}
@@ -223,8 +225,12 @@ void CoreEngine::HandleBlockEdge(const BlockEdge &L, ExplodedNode *Pred) {
// Get return statement..
const ReturnStmt *RS = nullptr;
if (!L.getSrc()->empty()) {
- if (Optional<CFGStmt> LastStmt = L.getSrc()->back().getAs<CFGStmt>()) {
+ CFGElement LastElement = L.getSrc()->back();
+ if (Optional<CFGStmt> LastStmt = LastElement.getAs<CFGStmt>()) {
RS = dyn_cast<ReturnStmt>(LastStmt->getStmt());
+ } else if (Optional<CFGAutomaticObjDtor> AutoDtor =
+ LastElement.getAs<CFGAutomaticObjDtor>()) {
+ RS = dyn_cast<ReturnStmt>(AutoDtor->getTriggerStmt());
}
}
@@ -392,8 +398,8 @@ void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term,
assert(B->succ_size() == 2);
NodeBuilderContext Ctx(*this, B, Pred);
ExplodedNodeSet Dst;
- SubEng.processBranch(Cond, Term, Ctx, Pred, Dst,
- *(B->succ_begin()), *(B->succ_begin()+1));
+ SubEng.processBranch(Cond, Ctx, Pred, Dst, *(B->succ_begin()),
+ *(B->succ_begin() + 1));
// Enqueue the new frontier onto the worklist.
enqueue(Dst);
}
diff --git a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp
index 530933916889..da7854df1def 100644
--- a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp
+++ b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp
@@ -77,5 +77,10 @@ void printDynamicTypeInfo(ProgramStateRef State, raw_ostream &Out,
}
}
+void *ProgramStateTrait<DynamicTypeMap>::GDMIndex() {
+ static int index = 0;
+ return &index;
+}
+
} // namespace ento
} // namespace clang
diff --git a/lib/StaticAnalyzer/Core/Environment.cpp b/lib/StaticAnalyzer/Core/Environment.cpp
index eccaee292c40..b45f93b6dde8 100644
--- a/lib/StaticAnalyzer/Core/Environment.cpp
+++ b/lib/StaticAnalyzer/Core/Environment.cpp
@@ -44,6 +44,9 @@ static const Expr *ignoreTransparentExprs(const Expr *E) {
case Stmt::ExprWithCleanupsClass:
E = cast<ExprWithCleanups>(E)->getSubExpr();
break;
+ case Stmt::ConstantExprClass:
+ E = cast<ConstantExpr>(E)->getSubExpr();
+ break;
case Stmt::CXXBindTemporaryExprClass:
E = cast<CXXBindTemporaryExpr>(E)->getSubExpr();
break;
@@ -89,6 +92,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry,
case Stmt::ExprWithCleanupsClass:
case Stmt::GenericSelectionExprClass:
case Stmt::OpaqueValueExprClass:
+ case Stmt::ConstantExprClass:
case Stmt::ParenExprClass:
case Stmt::SubstNonTypeTemplateParmExprClass:
llvm_unreachable("Should have been handled by ignoreTransparentExprs");
@@ -189,11 +193,6 @@ EnvironmentManager::removeDeadBindings(Environment Env,
// Mark all symbols in the block expr's value live.
RSScaner.scan(X);
- continue;
- } else {
- SymExpr::symbol_iterator SI = X.symbol_begin(), SE = X.symbol_end();
- for (; SI != SE; ++SI)
- SymReaper.maybeDead(*SI);
}
}
@@ -202,7 +201,9 @@ EnvironmentManager::removeDeadBindings(Environment Env,
}
void Environment::print(raw_ostream &Out, const char *NL,
- const char *Sep, const LocationContext *WithLC) const {
+ const char *Sep,
+ const ASTContext &Context,
+ const LocationContext *WithLC) const {
if (ExprBindings.isEmpty())
return;
@@ -222,10 +223,9 @@ void Environment::print(raw_ostream &Out, const char *NL,
assert(WithLC);
- LangOptions LO; // FIXME.
- PrintingPolicy PP(LO);
+ PrintingPolicy PP = Context.getPrintingPolicy();
- Out << NL << NL << "Expressions by stack frame:" << NL;
+ Out << NL << "Expressions by stack frame:" << NL;
WithLC->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) {
for (auto I : ExprBindings) {
if (I.first.getLocationContext() != LC)
@@ -234,8 +234,8 @@ void Environment::print(raw_ostream &Out, const char *NL,
const Stmt *S = I.first.getStmt();
assert(S != nullptr && "Expected non-null Stmt");
- Out << "(" << (const void *)LC << ',' << (const void *)S << ") ";
- S->printPretty(Out, nullptr, PP);
+ Out << "(LC" << LC->getID() << ", S" << S->getID(Context) << ") ";
+ S->printPretty(Out, /*Helper=*/nullptr, PP);
Out << " : " << I.second << NL;
}
});
diff --git a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
index ece103d9d09a..d6bcbb96b55f 100644
--- a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
+++ b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp
@@ -36,23 +36,6 @@ using namespace clang;
using namespace ento;
//===----------------------------------------------------------------------===//
-// Node auditing.
-//===----------------------------------------------------------------------===//
-
-// An out of line virtual method to provide a home for the class vtable.
-ExplodedNode::Auditor::~Auditor() = default;
-
-#ifndef NDEBUG
-static ExplodedNode::Auditor* NodeAuditor = nullptr;
-#endif
-
-void ExplodedNode::SetAuditor(ExplodedNode::Auditor* A) {
-#ifndef NDEBUG
- NodeAuditor = A;
-#endif
-}
-
-//===----------------------------------------------------------------------===//
// Cleanup.
//===----------------------------------------------------------------------===//
@@ -224,9 +207,6 @@ void ExplodedNode::addPredecessor(ExplodedNode *V, ExplodedGraph &G) {
assert(!V->isSink());
Preds.addNode(V, G);
V->Succs.addNode(this, G);
-#ifndef NDEBUG
- if (NodeAuditor) NodeAuditor->AddEdge(V, this);
-#endif
}
void ExplodedNode::NodeGroup::replaceNode(ExplodedNode *node) {
@@ -303,6 +283,16 @@ ExplodedNode * const *ExplodedNode::NodeGroup::end() const {
return Storage.getAddrOfPtr1() + 1;
}
+int64_t ExplodedNode::getID(ExplodedGraph *G) const {
+ return G->getAllocator().identifyKnownAlignedObject<ExplodedNode>(this);
+}
+
+bool ExplodedNode::isTrivial() const {
+ return pred_size() == 1 && succ_size() == 1 &&
+ getFirstPred()->getState()->getID() == getState()->getID() &&
+ getFirstPred()->succ_size() == 1;
+}
+
ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L,
ProgramStateRef State,
bool IsSink,
diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 2b4bdd754fdb..151eef56fece 100644
--- a/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -98,11 +98,12 @@ STATISTIC(NumMaxBlockCountReachedInInlined,
STATISTIC(NumTimesRetriedWithoutInlining,
"The # of times we re-evaluated a call without inlining");
-
//===----------------------------------------------------------------------===//
// Internal program state traits.
//===----------------------------------------------------------------------===//
+namespace {
+
// When modeling a C++ constructor, for a variety of reasons we need to track
// the location of the object for the duration of its ConstructionContext.
// ObjectsUnderConstruction maps statements within the construction context
@@ -137,9 +138,17 @@ public:
const ConstructionContextItem &getItem() const { return Impl.first; }
const LocationContext *getLocationContext() const { return Impl.second; }
+ ASTContext &getASTContext() const {
+ return getLocationContext()->getDecl()->getASTContext();
+ }
+
void print(llvm::raw_ostream &OS, PrinterHelper *Helper, PrintingPolicy &PP) {
- OS << '(' << getLocationContext() << ',' << getAnyASTNodePtr() << ','
- << getItem().getKindAsString();
+ OS << "(LC" << getLocationContext()->getID() << ',';
+ if (const Stmt *S = getItem().getStmtOrNull())
+ OS << 'S' << S->getID(getASTContext());
+ else
+ OS << 'I' << getItem().getCXXCtorInitializer()->getID(getASTContext());
+ OS << ',' << getItem().getKindAsString();
if (getItem().getKind() == ConstructionContextItem::ArgumentKind)
OS << " #" << getItem().getIndex();
OS << ") ";
@@ -164,6 +173,7 @@ public:
return Impl < RHS.Impl;
}
};
+} // namespace
typedef llvm::ImmutableMap<ConstructedObjectKey, SVal>
ObjectsUnderConstructionMap;
@@ -177,7 +187,7 @@ REGISTER_TRAIT_WITH_PROGRAMSTATE(ObjectsUnderConstruction,
static const char* TagProviderName = "ExprEngine";
ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU,
- AnalysisManager &mgr, bool gcEnabled,
+ AnalysisManager &mgr,
SetOfConstDecls *VisitedCalleesIn,
FunctionSummariesTy *FS,
InliningModes HowToInlineIn)
@@ -189,11 +199,11 @@ ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU,
this),
SymMgr(StateMgr.getSymbolManager()),
svalBuilder(StateMgr.getSValBuilder()), ObjCNoRet(mgr.getASTContext()),
- ObjCGCEnabled(gcEnabled), BR(mgr, *this),
+ BR(mgr, *this),
VisitedCallees(VisitedCalleesIn), HowToInline(HowToInlineIn) {
- unsigned TrimInterval = mgr.options.getGraphTrimInterval();
+ unsigned TrimInterval = mgr.options.GraphTrimInterval;
if (TrimInterval != 0) {
- // Enable eager node reclaimation when constructing the ExplodedGraph.
+ // Enable eager node reclamation when constructing the ExplodedGraph.
G.enableNodeReclamation(TrimInterval);
}
}
@@ -283,11 +293,10 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) {
return state;
}
-ProgramStateRef
-ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State,
- const LocationContext *LC,
- const Expr *InitWithAdjustments,
- const Expr *Result) {
+ProgramStateRef ExprEngine::createTemporaryRegionIfNeeded(
+ ProgramStateRef State, const LocationContext *LC,
+ const Expr *InitWithAdjustments, const Expr *Result,
+ const SubRegion **OutRegionWithAdjustments) {
// FIXME: This function is a hack that works around the quirky AST
// we're often having with respect to C++ temporaries. If only we modelled
// the actual execution order of statements properly in the CFG,
@@ -297,8 +306,11 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State,
if (!Result) {
// If we don't have an explicit result expression, we're in "if needed"
// mode. Only create a region if the current value is a NonLoc.
- if (!InitValWithAdjustments.getAs<NonLoc>())
+ if (!InitValWithAdjustments.getAs<NonLoc>()) {
+ if (OutRegionWithAdjustments)
+ *OutRegionWithAdjustments = nullptr;
return State;
+ }
Result = InitWithAdjustments;
} else {
// We need to create a region no matter what. For sanity, make sure we don't
@@ -418,11 +430,17 @@ ExprEngine::createTemporaryRegionIfNeeded(ProgramStateRef State,
// The result expression would now point to the correct sub-region of the
// newly created temporary region. Do this last in order to getSVal of Init
// correctly in case (Result == Init).
- State = State->BindExpr(Result, LC, Reg);
+ if (Result->isGLValue()) {
+ State = State->BindExpr(Result, LC, Reg);
+ } else {
+ State = State->BindExpr(Result, LC, InitValWithAdjustments);
+ }
// Notify checkers once for two bindLoc()s.
State = processRegionChange(State, TR, LC);
+ if (OutRegionWithAdjustments)
+ *OutRegionWithAdjustments = cast<SubRegion>(Reg.getAsRegion());
return State;
}
@@ -523,7 +541,6 @@ ExprEngine::processRegionChanges(ProgramStateRef state,
static void printObjectsUnderConstructionForContext(raw_ostream &Out,
ProgramStateRef State,
const char *NL,
- const char *Sep,
const LocationContext *LC) {
PrintingPolicy PP =
LC->getAnalysisDeclContext()->getASTContext().getPrintingPolicy();
@@ -545,7 +562,7 @@ void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State,
Out << Sep << "Objects under construction:" << NL;
LCtx->dumpStack(Out, "", NL, Sep, [&](const LocationContext *LC) {
- printObjectsUnderConstructionForContext(Out, State, NL, Sep, LC);
+ printObjectsUnderConstructionForContext(Out, State, NL, LC);
});
}
}
@@ -553,7 +570,7 @@ void ExprEngine::printState(raw_ostream &Out, ProgramStateRef State,
getCheckerManager().runCheckersForPrintState(Out, State, NL, Sep);
}
-void ExprEngine::processEndWorklist(bool hasWorkRemaining) {
+void ExprEngine::processEndWorklist() {
getCheckerManager().runCheckersForEndAnalysis(G, BR, *this);
}
@@ -666,44 +683,35 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out,
// Process any special transfer function for dead symbols.
// A tag to track convenience transitions, which can be removed at cleanup.
static SimpleProgramPointTag cleanupTag(TagProviderName, "Clean Node");
- if (!SymReaper.hasDeadSymbols()) {
- // Generate a CleanedNode that has the environment and store cleaned
- // up. Since no symbols are dead, we can optimize and not clean out
- // the constraint manager.
- StmtNodeBuilder Bldr(Pred, Out, *currBldrCtx);
- Bldr.generateNode(DiagnosticStmt, Pred, CleanedState, &cleanupTag, K);
-
- } else {
- // Call checkers with the non-cleaned state so that they could query the
- // values of the soon to be dead symbols.
- ExplodedNodeSet CheckedSet;
- getCheckerManager().runCheckersForDeadSymbols(CheckedSet, Pred, SymReaper,
- DiagnosticStmt, *this, K);
-
- // For each node in CheckedSet, generate CleanedNodes that have the
- // environment, the store, and the constraints cleaned up but have the
- // user-supplied states as the predecessors.
- StmtNodeBuilder Bldr(CheckedSet, Out, *currBldrCtx);
- for (const auto I : CheckedSet) {
- ProgramStateRef CheckerState = I->getState();
-
- // The constraint manager has not been cleaned up yet, so clean up now.
- CheckerState = getConstraintManager().removeDeadBindings(CheckerState,
- SymReaper);
-
- assert(StateMgr.haveEqualEnvironments(CheckerState, Pred->getState()) &&
- "Checkers are not allowed to modify the Environment as a part of "
- "checkDeadSymbols processing.");
- assert(StateMgr.haveEqualStores(CheckerState, Pred->getState()) &&
- "Checkers are not allowed to modify the Store as a part of "
- "checkDeadSymbols processing.");
-
- // Create a state based on CleanedState with CheckerState GDM and
- // generate a transition to that state.
- ProgramStateRef CleanedCheckerSt =
+ // Call checkers with the non-cleaned state so that they could query the
+ // values of the soon to be dead symbols.
+ ExplodedNodeSet CheckedSet;
+ getCheckerManager().runCheckersForDeadSymbols(CheckedSet, Pred, SymReaper,
+ DiagnosticStmt, *this, K);
+
+ // For each node in CheckedSet, generate CleanedNodes that have the
+ // environment, the store, and the constraints cleaned up but have the
+ // user-supplied states as the predecessors.
+ StmtNodeBuilder Bldr(CheckedSet, Out, *currBldrCtx);
+ for (const auto I : CheckedSet) {
+ ProgramStateRef CheckerState = I->getState();
+
+ // The constraint manager has not been cleaned up yet, so clean up now.
+ CheckerState =
+ getConstraintManager().removeDeadBindings(CheckerState, SymReaper);
+
+ assert(StateMgr.haveEqualEnvironments(CheckerState, Pred->getState()) &&
+ "Checkers are not allowed to modify the Environment as a part of "
+ "checkDeadSymbols processing.");
+ assert(StateMgr.haveEqualStores(CheckerState, Pred->getState()) &&
+ "Checkers are not allowed to modify the Store as a part of "
+ "checkDeadSymbols processing.");
+
+ // Create a state based on CleanedState with CheckerState GDM and
+ // generate a transition to that state.
+ ProgramStateRef CleanedCheckerSt =
StateMgr.getPersistentStateWithGDM(CleanedState, CheckerState);
- Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K);
- }
+ Bldr.generateNode(DiagnosticStmt, I, CleanedCheckerSt, &cleanupTag, K);
}
}
@@ -712,7 +720,7 @@ void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
G.reclaimRecentlyAllocatedNodes();
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
- currStmt->getLocStart(),
+ currStmt->getBeginLoc(),
"Error evaluating statement");
// Remove dead bindings and symbols.
@@ -739,14 +747,14 @@ void ExprEngine::ProcessStmt(const Stmt *currStmt, ExplodedNode *Pred) {
void ExprEngine::ProcessLoopExit(const Stmt* S, ExplodedNode *Pred) {
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
- S->getLocStart(),
+ S->getBeginLoc(),
"Error evaluating end of the loop");
ExplodedNodeSet Dst;
Dst.Add(Pred);
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
ProgramStateRef NewState = Pred->getState();
- if(AMgr.options.shouldUnrollLoops())
+ if(AMgr.options.ShouldUnrollLoops)
NewState = processLoopEnd(S, NewState);
LoopExit PP(S, Pred->getLocationContext());
@@ -878,12 +886,12 @@ void ExprEngine::ProcessNewAllocator(const CXXNewExpr *NE,
// TODO: We're not evaluating allocators for all cases just yet as
// we're not handling the return value correctly, which causes false
// positives when the alpha.cplusplus.NewDeleteLeaks check is on.
- if (Opts.mayInlineCXXAllocator())
+ if (Opts.MayInlineCXXAllocator)
VisitCXXNewAllocatorCall(NE, Pred, Dst);
else {
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
const LocationContext *LCtx = Pred->getLocationContext();
- PostImplicitCall PP(NE->getOperatorNew(), NE->getLocStart(), LCtx);
+ PostImplicitCall PP(NE->getOperatorNew(), NE->getBeginLoc(), LCtx);
Bldr.generateNode(PP, Pred->getState(), Pred);
}
Engine.enqueue(Dst, currBldrCtx->getBlock(), currStmtIdx);
@@ -940,7 +948,7 @@ void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor,
const CXXRecordDecl *RD = BTy->getAsCXXRecordDecl();
const CXXDestructorDecl *Dtor = RD->getDestructor();
- PostImplicitCall PP(Dtor, DE->getLocStart(), LCtx);
+ PostImplicitCall PP(Dtor, DE->getBeginLoc(), LCtx);
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
Bldr.generateNode(PP, Pred->getState(), Pred);
return;
@@ -1025,13 +1033,13 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D,
MR = V->getAsRegion();
}
- // If copy elision has occured, and the constructor corresponding to the
+ // If copy elision has occurred, and the constructor corresponding to the
// destructor was elided, we need to skip the destructor as well.
if (isDestructorElided(State, BTE, LC)) {
State = cleanupElidedDestructor(State, BTE, LC);
NodeBuilder Bldr(Pred, Dst, *currBldrCtx);
PostImplicitCall PP(D.getDestructorDecl(getContext()),
- D.getBindTemporaryExpr()->getLocStart(),
+ D.getBindTemporaryExpr()->getBeginLoc(),
Pred->getLocationContext());
Bldr.generateNode(PP, State, Pred);
return;
@@ -1093,7 +1101,7 @@ void ExprEngine::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE,
// This is a fallback solution in case we didn't have a construction
// context when we were constructing the temporary. Otherwise the map should
// have been populated there.
- if (!getAnalysisManager().options.includeTemporaryDtorsInCFG()) {
+ if (!getAnalysisManager().options.ShouldIncludeTemporaryDtorsInCFG) {
// In case we don't have temporary destructors in the CFG, do not mark
// the initialization - we would otherwise never clean it up.
Dst = PreVisit;
@@ -1120,7 +1128,7 @@ ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V,
InvalidatedSymbols Symbols;
public:
- explicit CollectReachableSymbolsCallback(ProgramStateRef State) {}
+ explicit CollectReachableSymbolsCallback(ProgramStateRef) {}
const InvalidatedSymbols &getSymbols() const { return Symbols; }
@@ -1139,8 +1147,7 @@ ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V,
void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
ExplodedNodeSet &DstTop) {
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
- S->getLocStart(),
- "Error evaluating statement");
+ S->getBeginLoc(), "Error evaluating statement");
ExplodedNodeSet Dst;
StmtNodeBuilder Bldr(Pred, DstTop, *currBldrCtx);
@@ -1274,6 +1281,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
Bldr.addNodes(Dst);
break;
+ case Expr::ConstantExprClass:
case Stmt::ExprWithCleanupsClass:
// Handled due to fully linearised CFG.
break;
@@ -1454,7 +1462,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
break;
case Stmt::LambdaExprClass:
- if (AMgr.options.shouldInlineLambdas()) {
+ if (AMgr.options.ShouldInlineLambdas) {
Bldr.takeNodes(Pred);
VisitLambdaExpr(cast<LambdaExpr>(S), Pred, Dst);
Bldr.addNodes(Dst);
@@ -1483,7 +1491,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
Bldr.takeNodes(Pred);
- if (AMgr.options.eagerlyAssumeBinOpBifurcation &&
+ if (AMgr.options.ShouldEagerlyAssume &&
(B->isRelationalOp() || B->isEqualityOp())) {
ExplodedNodeSet Tmp;
VisitBinaryOperator(cast<BinaryOperator>(S), Pred, Tmp);
@@ -1747,7 +1755,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
case Stmt::UnaryOperatorClass: {
Bldr.takeNodes(Pred);
const auto *U = cast<UnaryOperator>(S);
- if (AMgr.options.eagerlyAssumeBinOpBifurcation && (U->getOpcode() == UO_LNot)) {
+ if (AMgr.options.ShouldEagerlyAssume && (U->getOpcode() == UO_LNot)) {
ExplodedNodeSet Tmp;
VisitUnaryOperator(U, Pred, Tmp);
evalEagerlyAssumeBinOpBifurcation(Dst, Tmp, U);
@@ -1848,7 +1856,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L,
PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext());
// If we reach a loop which has a known bound (and meets
// other constraints) then consider completely unrolling it.
- if(AMgr.options.shouldUnrollLoops()) {
+ if(AMgr.options.ShouldUnrollLoops) {
unsigned maxBlockVisitOnPath = AMgr.options.maxBlockVisitOnPath;
const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminator();
if (Term) {
@@ -1870,7 +1878,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L,
// maximum number of times, widen the loop.
unsigned int BlockCount = nodeBuilder.getContext().blockCount();
if (BlockCount == AMgr.options.maxBlockVisitOnPath - 1 &&
- AMgr.options.shouldWidenLoops()) {
+ AMgr.options.ShouldWidenLoops) {
const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminator();
if (!(Term &&
(isa<ForStmt>(Term) || isa<WhileStmt>(Term) || isa<DoStmt>(Term))))
@@ -1923,8 +1931,7 @@ void ExprEngine::processCFGBlockEntrance(const BlockEdge &L,
/// integers that promote their values (which are currently not tracked well).
/// This function returns the SVal bound to Condition->IgnoreCasts if all the
// cast(s) did was sign-extend the original value.
-static SVal RecoverCastedSymbol(ProgramStateManager& StateMgr,
- ProgramStateRef state,
+static SVal RecoverCastedSymbol(ProgramStateRef state,
const Stmt *Condition,
const LocationContext *LCtx,
ASTContext &Ctx) {
@@ -2021,7 +2028,7 @@ static const Stmt *ResolveCondition(const Stmt *Condition,
llvm_unreachable("could not resolve condition");
}
-void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term,
+void ExprEngine::processBranch(const Stmt *Condition,
NodeBuilderContext& BldCtx,
ExplodedNode *Pred,
ExplodedNodeSet &Dst,
@@ -2046,7 +2053,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term,
Condition = ResolveCondition(Condition, BldCtx.getBlock());
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
- Condition->getLocStart(),
+ Condition->getBeginLoc(),
"Error evaluating branch");
ExplodedNodeSet CheckersOutSet;
@@ -2072,8 +2079,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term,
// integers that promote their values are currently not tracked well.
// If 'Condition' is such an expression, try and recover the
// underlying value and use that instead.
- SVal recovered = RecoverCastedSymbol(getStateManager(),
- PrevState, Condition,
+ SVal recovered = RecoverCastedSymbol(PrevState, Condition,
PredI->getLocationContext(),
getContext());
@@ -2200,17 +2206,21 @@ void ExprEngine::processBeginOfFunction(NodeBuilderContext &BC,
void ExprEngine::processEndOfFunction(NodeBuilderContext& BC,
ExplodedNode *Pred,
const ReturnStmt *RS) {
+ ProgramStateRef State = Pred->getState();
+
+ if (!Pred->getStackFrame()->inTopFrame())
+ State = finishArgumentConstruction(
+ State, *getStateManager().getCallEventManager().getCaller(
+ Pred->getStackFrame(), Pred->getState()));
+
// FIXME: We currently cannot assert that temporaries are clear, because
// lifetime extended temporaries are not always modelled correctly. In some
// cases when we materialize the temporary, we do
// createTemporaryRegionIfNeeded(), and the region changes, and also the
// respective destructor becomes automatic from temporary. So for now clean up
- // the state manually before asserting. Ideally, the code above the assertion
- // should go away, but the assertion should remain.
+ // the state manually before asserting. Ideally, this braced block of code
+ // should go away.
{
- ExplodedNodeSet CleanUpObjects;
- NodeBuilder Bldr(Pred, CleanUpObjects, BC);
- ProgramStateRef State = Pred->getState();
const LocationContext *FromLC = Pred->getLocationContext();
const LocationContext *ToLC = FromLC->getStackFrame()->getParent();
const LocationContext *LC = FromLC;
@@ -2229,15 +2239,20 @@ void ExprEngine::processEndOfFunction(NodeBuilderContext& BC,
}
LC = LC->getParent();
}
- if (State != Pred->getState()) {
- Pred = Bldr.generateNode(Pred->getLocation(), State, Pred);
- if (!Pred) {
- // The node with clean temporaries already exists. We might have reached
- // it on a path on which we initialize different temporaries.
- return;
- }
+ }
+
+ // Perform the transition with cleanups.
+ if (State != Pred->getState()) {
+ ExplodedNodeSet PostCleanup;
+ NodeBuilder Bldr(Pred, PostCleanup, BC);
+ Pred = Bldr.generateNode(Pred->getLocation(), State, Pred);
+ if (!Pred) {
+ // The node with clean temporaries already exists. We might have reached
+ // it on a path on which we initialize different temporaries.
+ return;
}
}
+
assert(areAllObjectsFullyConstructed(Pred->getState(),
Pred->getLocationContext(),
Pred->getStackFrame()->getParent()));
@@ -2364,7 +2379,7 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D,
const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex);
Optional<std::pair<SVal, QualType>> VInfo;
- if (AMgr.options.shouldInlineLambdas() && DeclRefEx &&
+ if (AMgr.options.ShouldInlineLambdas && DeclRefEx &&
DeclRefEx->refersToEnclosingVariableOrCapture() && MD &&
MD->getParent()->isLambda()) {
// Lookup the field of the lambda.
@@ -2524,8 +2539,12 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred,
}
// Handle regular struct fields / member variables.
- state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr);
- SVal baseExprVal = state->getSVal(BaseExpr, LCtx);
+ const SubRegion *MR = nullptr;
+ state = createTemporaryRegionIfNeeded(state, LCtx, BaseExpr,
+ /*Result=*/nullptr,
+ /*OutRegionWithAdjustments=*/&MR);
+ SVal baseExprVal =
+ MR ? loc::MemRegionVal(MR) : state->getSVal(BaseExpr, LCtx);
const auto *field = cast<FieldDecl>(Member);
SVal L = state->getLValue(field, baseExprVal);
@@ -2645,7 +2664,6 @@ ProgramStateRef
ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State,
const InvalidatedSymbols *Invalidated,
ArrayRef<const MemRegion *> ExplicitRegions,
- ArrayRef<const MemRegion *> Regions,
const CallEvent *Call,
RegionAndSymbolInvalidationTraits &ITraits) {
if (!Invalidated || Invalidated->empty())
@@ -2755,7 +2773,7 @@ void ExprEngine::evalStore(ExplodedNodeSet &Dst, const Expr *AssignE,
// Evaluate the location (checks for bad dereferences).
ExplodedNodeSet Tmp;
- evalLocation(Tmp, AssignE, LocationE, Pred, state, location, tag, false);
+ evalLocation(Tmp, AssignE, LocationE, Pred, state, location, false);
if (Tmp.empty())
return;
@@ -2780,7 +2798,7 @@ void ExprEngine::evalLoad(ExplodedNodeSet &Dst,
assert(BoundEx);
// Evaluate the location (checks for bad dereferences).
ExplodedNodeSet Tmp;
- evalLocation(Tmp, NodeEx, BoundEx, Pred, state, location, tag, true);
+ evalLocation(Tmp, NodeEx, BoundEx, Pred, state, location, true);
if (Tmp.empty())
return;
@@ -2811,7 +2829,6 @@ void ExprEngine::evalLocation(ExplodedNodeSet &Dst,
ExplodedNode *Pred,
ProgramStateRef state,
SVal location,
- const ProgramPointTag *tag,
bool isLoad) {
StmtNodeBuilder BldrTop(Pred, Dst, *currBldrCtx);
// Early checks for performance reason.
@@ -2927,211 +2944,108 @@ void ExprEngine::VisitMSAsmStmt(const MSAsmStmt *A, ExplodedNode *Pred,
//===----------------------------------------------------------------------===//
#ifndef NDEBUG
-static ExprEngine* GraphPrintCheckerState;
-static SourceManager* GraphPrintSourceManager;
-
namespace llvm {
template<>
-struct DOTGraphTraits<ExplodedNode*> : public DefaultDOTGraphTraits {
+struct DOTGraphTraits<ExplodedGraph*> : public DefaultDOTGraphTraits {
DOTGraphTraits (bool isSimple = false) : DefaultDOTGraphTraits(isSimple) {}
- // FIXME: Since we do not cache error nodes in ExprEngine now, this does not
- // work.
- static std::string getNodeAttributes(const ExplodedNode *N, void*) {
- return {};
- }
-
- // De-duplicate some source location pretty-printing.
- static void printLocation(raw_ostream &Out, SourceLocation SLoc) {
- if (SLoc.isFileID()) {
- Out << "\\lline="
- << GraphPrintSourceManager->getExpansionLineNumber(SLoc)
- << " col="
- << GraphPrintSourceManager->getExpansionColumnNumber(SLoc)
- << "\\l";
- }
- }
-
- static std::string getNodeLabel(const ExplodedNode *N, void*){
- std::string sbuf;
- llvm::raw_string_ostream Out(sbuf);
-
- // Program Location.
- ProgramPoint Loc = N->getLocation();
-
- switch (Loc.getKind()) {
- case ProgramPoint::BlockEntranceKind:
- Out << "Block Entrance: B"
- << Loc.castAs<BlockEntrance>().getBlock()->getBlockID();
- break;
-
- case ProgramPoint::BlockExitKind:
- assert(false);
- break;
-
- case ProgramPoint::CallEnterKind:
- Out << "CallEnter";
- break;
-
- case ProgramPoint::CallExitBeginKind:
- Out << "CallExitBegin";
- break;
+ static bool nodeHasBugReport(const ExplodedNode *N) {
+ BugReporter &BR = static_cast<ExprEngine &>(
+ N->getState()->getStateManager().getOwningEngine()).getBugReporter();
- case ProgramPoint::CallExitEndKind:
- Out << "CallExitEnd";
- break;
-
- case ProgramPoint::PostStmtPurgeDeadSymbolsKind:
- Out << "PostStmtPurgeDeadSymbols";
- break;
-
- case ProgramPoint::PreStmtPurgeDeadSymbolsKind:
- Out << "PreStmtPurgeDeadSymbols";
- break;
-
- case ProgramPoint::EpsilonKind:
- Out << "Epsilon Point";
- break;
+ const auto EQClasses =
+ llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end());
- case ProgramPoint::LoopExitKind: {
- LoopExit LE = Loc.castAs<LoopExit>();
- Out << "LoopExit: " << LE.getLoopStmt()->getStmtClassName();
- break;
+ for (const auto &EQ : EQClasses) {
+ for (const BugReport &Report : EQ) {
+ if (Report.getErrorNode() == N)
+ return true;
}
+ }
+ return false;
+ }
- case ProgramPoint::PreImplicitCallKind: {
- ImplicitCallPoint PC = Loc.castAs<ImplicitCallPoint>();
- Out << "PreCall: ";
-
- // FIXME: Get proper printing options.
- PC.getDecl()->print(Out, LangOptions());
- printLocation(Out, PC.getLocation());
- break;
- }
-
- case ProgramPoint::PostImplicitCallKind: {
- ImplicitCallPoint PC = Loc.castAs<ImplicitCallPoint>();
- Out << "PostCall: ";
-
- // FIXME: Get proper printing options.
- PC.getDecl()->print(Out, LangOptions());
- printLocation(Out, PC.getLocation());
- break;
- }
-
- case ProgramPoint::PostInitializerKind: {
- Out << "PostInitializer: ";
- const CXXCtorInitializer *Init =
- Loc.castAs<PostInitializer>().getInitializer();
- if (const FieldDecl *FD = Init->getAnyMember())
- Out << *FD;
- else {
- QualType Ty = Init->getTypeSourceInfo()->getType();
- Ty = Ty.getLocalUnqualifiedType();
- LangOptions LO; // FIXME.
- Ty.print(Out, LO);
- }
- break;
- }
-
- case ProgramPoint::BlockEdgeKind: {
- const BlockEdge &E = Loc.castAs<BlockEdge>();
- Out << "Edge: (B" << E.getSrc()->getBlockID() << ", B"
- << E.getDst()->getBlockID() << ')';
-
- if (const Stmt *T = E.getSrc()->getTerminator()) {
- SourceLocation SLoc = T->getLocStart();
-
- Out << "\\|Terminator: ";
- LangOptions LO; // FIXME.
- E.getSrc()->printTerminator(Out, LO);
-
- if (SLoc.isFileID()) {
- Out << "\\lline="
- << GraphPrintSourceManager->getExpansionLineNumber(SLoc)
- << " col="
- << GraphPrintSourceManager->getExpansionColumnNumber(SLoc);
- }
-
- if (isa<SwitchStmt>(T)) {
- const Stmt *Label = E.getDst()->getLabel();
-
- if (Label) {
- if (const auto *C = dyn_cast<CaseStmt>(Label)) {
- Out << "\\lcase ";
- LangOptions LO; // FIXME.
- if (C->getLHS())
- C->getLHS()->printPretty(Out, nullptr, PrintingPolicy(LO));
-
- if (const Stmt *RHS = C->getRHS()) {
- Out << " .. ";
- RHS->printPretty(Out, nullptr, PrintingPolicy(LO));
- }
-
- Out << ":";
- }
- else {
- assert(isa<DefaultStmt>(Label));
- Out << "\\ldefault:";
- }
- }
- else
- Out << "\\l(implicit) default:";
- }
- else if (isa<IndirectGotoStmt>(T)) {
- // FIXME
- }
- else {
- Out << "\\lCondition: ";
- if (*E.getSrc()->succ_begin() == E.getDst())
- Out << "true";
- else
- Out << "false";
- }
-
- Out << "\\l";
- }
+ /// \p PreCallback: callback before break.
+ /// \p PostCallback: callback after break.
+ /// \p Stop: stop iteration if returns {@code true}
+ /// \return Whether {@code Stop} ever returned {@code true}.
+ static bool traverseHiddenNodes(
+ const ExplodedNode *N,
+ llvm::function_ref<void(const ExplodedNode *)> PreCallback,
+ llvm::function_ref<void(const ExplodedNode *)> PostCallback,
+ llvm::function_ref<bool(const ExplodedNode *)> Stop) {
+ const ExplodedNode *FirstHiddenNode = N;
+ while (FirstHiddenNode->pred_size() == 1 &&
+ isNodeHidden(*FirstHiddenNode->pred_begin())) {
+ FirstHiddenNode = *FirstHiddenNode->pred_begin();
+ }
+ const ExplodedNode *OtherNode = FirstHiddenNode;
+ while (true) {
+ PreCallback(OtherNode);
+ if (Stop(OtherNode))
+ return true;
+ if (OtherNode == N)
break;
- }
+ PostCallback(OtherNode);
- default: {
- const Stmt *S = Loc.castAs<StmtPoint>().getStmt();
- assert(S != nullptr && "Expecting non-null Stmt");
-
- Out << S->getStmtClassName() << ' ' << (const void*) S << ' ';
- LangOptions LO; // FIXME.
- S->printPretty(Out, nullptr, PrintingPolicy(LO));
- printLocation(Out, S->getLocStart());
-
- if (Loc.getAs<PreStmt>())
- Out << "\\lPreStmt\\l;";
- else if (Loc.getAs<PostLoad>())
- Out << "\\lPostLoad\\l;";
- else if (Loc.getAs<PostStore>())
- Out << "\\lPostStore\\l";
- else if (Loc.getAs<PostLValue>())
- Out << "\\lPostLValue\\l";
- else if (Loc.getAs<PostAllocatorCall>())
- Out << "\\lPostAllocatorCall\\l";
+ OtherNode = *OtherNode->succ_begin();
+ }
+ return false;
+ }
- break;
- }
+ static std::string getNodeAttributes(const ExplodedNode *N,
+ ExplodedGraph *) {
+ SmallVector<StringRef, 10> Out;
+ auto Noop = [](const ExplodedNode*){};
+ if (traverseHiddenNodes(N, Noop, Noop, &nodeHasBugReport)) {
+ Out.push_back("style=filled");
+ Out.push_back("fillcolor=red");
}
- ProgramStateRef state = N->getState();
- Out << "\\|StateID: " << (const void*) state.get()
- << " NodeID: " << (const void*) N << "\\|";
+ if (traverseHiddenNodes(N, Noop, Noop,
+ [](const ExplodedNode *C) { return C->isSink(); }))
+ Out.push_back("color=blue");
+ return llvm::join(Out, ",");
+ }
- state->printDOT(Out, N->getLocationContext());
+ static bool isNodeHidden(const ExplodedNode *N) {
+ return N->isTrivial();
+ }
- Out << "\\l";
+ static std::string getNodeLabel(const ExplodedNode *N, ExplodedGraph *G){
+ std::string sbuf;
+ llvm::raw_string_ostream Out(sbuf);
- if (const ProgramPointTag *tag = Loc.getTag()) {
- Out << "\\|Tag: " << tag->getTagDescription();
- Out << "\\l";
- }
+ ProgramStateRef State = N->getState();
+
+ // Dump program point for all the previously skipped nodes.
+ traverseHiddenNodes(
+ N,
+ [&](const ExplodedNode *OtherNode) {
+ OtherNode->getLocation().print(/*CR=*/"\\l", Out);
+ if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag())
+ Out << "\\lTag:" << Tag->getTagDescription();
+ if (N->isSink())
+ Out << "\\lNode is sink\\l";
+ if (nodeHasBugReport(N))
+ Out << "\\lBug report attached\\l";
+ },
+ [&](const ExplodedNode *) { Out << "\\l--------\\l"; },
+ [&](const ExplodedNode *) { return false; });
+
+ Out << "\\l\\|";
+
+ Out << "StateID: ST" << State->getID() << ", NodeID: N" << N->getID(G)
+ << " <" << (const void *)N << ">\\|";
+
+ bool SameAsAllPredecessors =
+ std::all_of(N->pred_begin(), N->pred_end(), [&](const ExplodedNode *P) {
+ return P->getState() == State;
+ });
+ if (!SameAsAllPredecessors)
+ State->printDOT(Out, N->getLocationContext());
return Out.str();
}
};
@@ -3141,48 +3055,61 @@ struct DOTGraphTraits<ExplodedNode*> : public DefaultDOTGraphTraits {
void ExprEngine::ViewGraph(bool trim) {
#ifndef NDEBUG
+ std::string Filename = DumpGraph(trim);
+ llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
+#endif
+ llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
+}
+
+
+void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) {
+#ifndef NDEBUG
+ std::string Filename = DumpGraph(Nodes);
+ llvm::DisplayGraph(Filename, false, llvm::GraphProgram::DOT);
+#endif
+ llvm::errs() << "Warning: viewing graph requires assertions" << "\n";
+}
+
+std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) {
+#ifndef NDEBUG
if (trim) {
std::vector<const ExplodedNode *> Src;
- // Flush any outstanding reports to make sure we cover all the nodes.
- // This does not cause them to get displayed.
- for (const auto I : BR)
- const_cast<BugType *>(I)->FlushReports(BR);
-
// Iterate through the reports and get their nodes.
for (BugReporter::EQClasses_iterator
EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) {
const auto *N = const_cast<ExplodedNode *>(EI->begin()->getErrorNode());
if (N) Src.push_back(N);
}
-
- ViewGraph(Src);
- }
- else {
- GraphPrintCheckerState = this;
- GraphPrintSourceManager = &getContext().getSourceManager();
-
- llvm::ViewGraph(*G.roots_begin(), "ExprEngine");
-
- GraphPrintCheckerState = nullptr;
- GraphPrintSourceManager = nullptr;
+ return DumpGraph(Src, Filename);
+ } else {
+ return llvm::WriteGraph(&G, "ExprEngine", /*ShortNames=*/false,
+ /*Title=*/"Exploded Graph", /*Filename=*/Filename);
}
#endif
+ llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
+ return "";
}
-void ExprEngine::ViewGraph(ArrayRef<const ExplodedNode*> Nodes) {
+std::string ExprEngine::DumpGraph(ArrayRef<const ExplodedNode*> Nodes,
+ StringRef Filename) {
#ifndef NDEBUG
- GraphPrintCheckerState = this;
- GraphPrintSourceManager = &getContext().getSourceManager();
-
std::unique_ptr<ExplodedGraph> TrimmedG(G.trim(Nodes));
- if (!TrimmedG.get())
+ if (!TrimmedG.get()) {
llvm::errs() << "warning: Trimmed ExplodedGraph is empty.\n";
- else
- llvm::ViewGraph(*TrimmedG->roots_begin(), "TrimmedExprEngine");
-
- GraphPrintCheckerState = nullptr;
- GraphPrintSourceManager = nullptr;
+ } else {
+ return llvm::WriteGraph(TrimmedG.get(), "TrimmedExprEngine",
+ /*ShortNames=*/false,
+ /*Title=*/"Trimmed Exploded Graph",
+ /*Filename=*/Filename);
+ }
#endif
+ llvm::errs() << "Warning: dumping graph requires assertions" << "\n";
+ return "";
+}
+
+void *ProgramStateTrait<ReplayWithoutInlining>::GDMIndex() {
+ static int index = 0;
+ return &index;
}
diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp
index 61b7a290e42a..b980628878e9 100644
--- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp
@@ -412,10 +412,11 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex,
case CK_BlockPointerToObjCPointerCast:
case CK_AnyPointerToBlockPointerCast:
case CK_ObjCObjectLValueCast:
- case CK_ZeroToOCLEvent:
- case CK_ZeroToOCLQueue:
+ case CK_ZeroToOCLOpaqueType:
case CK_IntToOCLSampler:
- case CK_LValueBitCast: {
+ case CK_LValueBitCast:
+ case CK_FixedPointCast:
+ case CK_FixedPointToBoolean: {
state =
handleLValueBitCast(state, Ex, LCtx, T, ExTy, CastE, Bldr, Pred);
continue;
@@ -809,8 +810,9 @@ void ExprEngine::
VisitOffsetOfExpr(const OffsetOfExpr *OOE,
ExplodedNode *Pred, ExplodedNodeSet &Dst) {
StmtNodeBuilder B(Pred, Dst, *currBldrCtx);
- APSInt IV;
- if (OOE->EvaluateAsInt(IV, getContext())) {
+ Expr::EvalResult Result;
+ if (OOE->EvaluateAsInt(Result, getContext())) {
+ APSInt IV = Result.Val.getInt();
assert(IV.getBitWidth() == getContext().getTypeSize(OOE->getType()));
assert(OOE->getType()->isBuiltinType());
assert(OOE->getType()->getAs<BuiltinType>()->isInteger());
@@ -956,7 +958,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred,
}
case UO_Plus:
assert(!U->isGLValue());
- // FALL-THROUGH.
+ LLVM_FALLTHROUGH;
case UO_Deref:
case UO_Extension: {
handleUOExtension(I, U, Bldr);
@@ -1050,7 +1052,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
// Perform the store, so that the uninitialized value detection happens.
Bldr.takeNodes(*I);
ExplodedNodeSet Dst3;
- evalStore(Dst3, U, U, *I, state, loc, V2_untested);
+ evalStore(Dst3, U, Ex, *I, state, loc, V2_untested);
Bldr.addNodes(Dst3);
continue;
@@ -1118,7 +1120,7 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U,
// Perform the store.
Bldr.takeNodes(*I);
ExplodedNodeSet Dst3;
- evalStore(Dst3, U, U, *I, state, loc, Result);
+ evalStore(Dst3, U, Ex, *I, state, loc, Result);
Bldr.addNodes(Dst3);
}
Dst.insert(Dst2);
diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
index 4f1766a813c6..6445b9df5a58 100644
--- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp
@@ -113,7 +113,9 @@ SVal ExprEngine::makeZeroElementRegion(ProgramStateRef State, SVal LValue,
std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
const Expr *E, ProgramStateRef State, const LocationContext *LCtx,
const ConstructionContext *CC, EvalCallOptions &CallOpts) {
- MemRegionManager &MRMgr = getSValBuilder().getRegionManager();
+ SValBuilder &SVB = getSValBuilder();
+ MemRegionManager &MRMgr = SVB.getRegionManager();
+ ASTContext &ACtx = SVB.getContext();
// See if we're constructing an existing region by looking at the
// current construction context.
@@ -139,7 +141,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
assert(Init->isAnyMemberInitializer());
const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl());
Loc ThisPtr =
- getSValBuilder().getCXXThis(CurCtor, LCtx->getStackFrame());
+ SVB.getCXXThis(CurCtor, LCtx->getStackFrame());
SVal ThisVal = State->getSVal(ThisPtr);
const ValueDecl *Field;
@@ -159,7 +161,7 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
return std::make_pair(State, FieldVal);
}
case ConstructionContext::NewAllocatedObjectKind: {
- if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
+ if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
const auto *NECC = cast<NewAllocatedObjectConstructionContext>(CC);
const auto *NE = NECC->getCXXNewExpr();
SVal V = *getObjectUnderConstruction(State, NE, LCtx);
@@ -199,18 +201,31 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
cast<Expr>(SFC->getCallSite()), State, CallerLCtx,
RTC->getConstructionContext(), CallOpts);
} else {
- // We are on the top frame of the analysis.
- // TODO: What exactly happens when we are? Does the temporary object
- // live long enough in the region store in this case? Would checkers
- // think that this object immediately goes out of scope?
- CallOpts.IsTemporaryCtorOrDtor = true;
- SVal V = loc::MemRegionVal(MRMgr.getCXXTempObjectRegion(E, LCtx));
+ // We are on the top frame of the analysis. We do not know where is the
+ // object returned to. Conjure a symbolic region for the return value.
+ // TODO: We probably need a new MemRegion kind to represent the storage
+ // of that SymbolicRegion, so that we cound produce a fancy symbol
+ // instead of an anonymous conjured symbol.
+ // TODO: Do we need to track the region to avoid having it dead
+ // too early? It does die too early, at least in C++17, but because
+ // putting anything into a SymbolicRegion causes an immediate escape,
+ // it doesn't cause any leak false positives.
+ const auto *RCC = cast<ReturnedValueConstructionContext>(CC);
+ // Make sure that this doesn't coincide with any other symbol
+ // conjured for the returned expression.
+ static const int TopLevelSymRegionTag = 0;
+ const Expr *RetE = RCC->getReturnStmt()->getRetValue();
+ assert(RetE && "Void returns should not have a construction context");
+ QualType ReturnTy = RetE->getType();
+ QualType RegionTy = ACtx.getPointerType(ReturnTy);
+ SVal V = SVB.conjureSymbolVal(&TopLevelSymRegionTag, RetE, SFC,
+ RegionTy, currBldrCtx->blockCount());
return std::make_pair(State, V);
}
llvm_unreachable("Unhandled return value construction context!");
}
case ConstructionContext::ElidedTemporaryObjectKind: {
- assert(AMgr.getAnalyzerOptions().shouldElideConstructors());
+ assert(AMgr.getAnalyzerOptions().ShouldElideConstructors);
const auto *TCC = cast<ElidedTemporaryObjectConstructionContext>(CC);
const CXXBindTemporaryExpr *BTE = TCC->getCXXBindTemporaryExpr();
const MaterializeTemporaryExpr *MTE = TCC->getMaterializedTemporaryExpr();
@@ -292,8 +307,75 @@ std::pair<ProgramStateRef, SVal> ExprEngine::prepareForObjectConstruction(
return std::make_pair(State, V);
}
case ConstructionContext::ArgumentKind: {
- // Function argument constructors. Not implemented yet.
- break;
+ // Arguments are technically temporaries.
+ CallOpts.IsTemporaryCtorOrDtor = true;
+
+ const auto *ACC = cast<ArgumentConstructionContext>(CC);
+ const Expr *E = ACC->getCallLikeExpr();
+ unsigned Idx = ACC->getIndex();
+ const CXXBindTemporaryExpr *BTE = ACC->getCXXBindTemporaryExpr();
+
+ CallEventManager &CEMgr = getStateManager().getCallEventManager();
+ SVal V = UnknownVal();
+ auto getArgLoc = [&](CallEventRef<> Caller) -> Optional<SVal> {
+ const LocationContext *FutureSFC = Caller->getCalleeStackFrame();
+ // Return early if we are unable to reliably foresee
+ // the future stack frame.
+ if (!FutureSFC)
+ return None;
+
+ // This should be equivalent to Caller->getDecl() for now, but
+ // FutureSFC->getDecl() is likely to support better stuff (like
+ // virtual functions) earlier.
+ const Decl *CalleeD = FutureSFC->getDecl();
+
+ // FIXME: Support for variadic arguments is not implemented here yet.
+ if (CallEvent::isVariadic(CalleeD))
+ return None;
+
+ // Operator arguments do not correspond to operator parameters
+ // because this-argument is implemented as a normal argument in
+ // operator call expressions but not in operator declarations.
+ const VarRegion *VR = Caller->getParameterLocation(
+ *Caller->getAdjustedParameterIndex(Idx));
+ if (!VR)
+ return None;
+
+ return loc::MemRegionVal(VR);
+ };
+
+ if (const auto *CE = dyn_cast<CallExpr>(E)) {
+ CallEventRef<> Caller = CEMgr.getSimpleCall(CE, State, LCtx);
+ if (auto OptV = getArgLoc(Caller))
+ V = *OptV;
+ else
+ break;
+ State = addObjectUnderConstruction(State, {CE, Idx}, LCtx, V);
+ } else if (const auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
+ // Don't bother figuring out the target region for the future
+ // constructor because we won't need it.
+ CallEventRef<> Caller =
+ CEMgr.getCXXConstructorCall(CCE, /*Target=*/nullptr, State, LCtx);
+ if (auto OptV = getArgLoc(Caller))
+ V = *OptV;
+ else
+ break;
+ State = addObjectUnderConstruction(State, {CCE, Idx}, LCtx, V);
+ } else if (const auto *ME = dyn_cast<ObjCMessageExpr>(E)) {
+ CallEventRef<> Caller = CEMgr.getObjCMethodCall(ME, State, LCtx);
+ if (auto OptV = getArgLoc(Caller))
+ V = *OptV;
+ else
+ break;
+ State = addObjectUnderConstruction(State, {ME, Idx}, LCtx, V);
+ }
+
+ assert(!V.isUnknown());
+
+ if (BTE)
+ State = addObjectUnderConstruction(State, BTE, LCtx, V);
+
+ return std::make_pair(State, V);
}
}
}
@@ -359,7 +441,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
}
}
}
- // FALLTHROUGH
+ LLVM_FALLTHROUGH;
case CXXConstructExpr::CK_NonVirtualBase:
// In C++17, classes with non-virtual bases may be aggregates, so they would
// be initialized as aggregates without a constructor call, so we may have
@@ -378,7 +460,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true;
break;
}
- // FALLTHROUGH
+ LLVM_FALLTHROUGH;
case CXXConstructExpr::CK_Delegating: {
const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl());
Loc ThisPtr = getSValBuilder().getCXXThis(CurCtor,
@@ -502,8 +584,15 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE,
}
}
+ ExplodedNodeSet DstPostArgumentCleanup;
+ for (auto I : DstEvaluated)
+ finishArgumentConstruction(DstPostArgumentCleanup, I, *Call);
+
+ // If there were other constructors called for object-type arguments
+ // of this constructor, clean them up.
ExplodedNodeSet DstPostCall;
- getCheckerManager().runCheckersForPostCall(DstPostCall, DstEvaluated,
+ getCheckerManager().runCheckersForPostCall(DstPostCall,
+ DstPostArgumentCleanup,
*Call, *this);
getCheckerManager().runCheckersForPostStmt(destNodes, DstPostCall, CE, *this);
}
@@ -551,7 +640,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE,
ProgramStateRef State = Pred->getState();
const LocationContext *LCtx = Pred->getLocationContext();
PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(),
- CNE->getStartLoc(),
+ CNE->getBeginLoc(),
"Error evaluating New Allocator Call");
CallEventManager &CEMgr = getStateManager().getCallEventManager();
CallEventRef<CXXAllocatorCall> Call =
@@ -632,7 +721,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
ProgramStateRef State = Pred->getState();
// Retrieve the stored operator new() return value.
- if (AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
+ if (AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
symVal = *getObjectUnderConstruction(State, CNE, LCtx);
State = finishObjectConstruction(State, CNE, LCtx);
}
@@ -652,7 +741,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred,
CallEventRef<CXXAllocatorCall> Call =
CEMgr.getCXXAllocatorCall(CNE, State, LCtx);
- if (!AMgr.getAnalyzerOptions().mayInlineCXXAllocator()) {
+ if (!AMgr.getAnalyzerOptions().MayInlineCXXAllocator) {
// Invalidate placement args.
// FIXME: Once we figure out how we want allocators to work,
// we should be using the usual pre-/(default-)eval-/post-call checks here.
diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
index 3ee67f3d6882..758195d8d911 100644
--- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp
@@ -349,7 +349,7 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) {
/*WasInlined=*/true);
} else if (CE &&
!(isa<CXXNewExpr>(CE) && // Called when visiting CXXNewExpr.
- AMgr.getAnalyzerOptions().mayInlineCXXAllocator())) {
+ AMgr.getAnalyzerOptions().MayInlineCXXAllocator)) {
getCheckerManager().runCheckersForPostStmt(Dst, DstPostCall, CE,
*this, /*WasInlined=*/true);
} else {
@@ -386,7 +386,7 @@ void ExprEngine::examineStackFrames(const Decl *D, const LocationContext *LCtx,
// Do not count the small functions when determining the stack depth.
AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(DI);
const CFG *CalleeCFG = CalleeADC->getCFG();
- if (CalleeCFG->getNumBlockIDs() > AMgr.options.getAlwaysInlineSize())
+ if (CalleeCFG->getNumBlockIDs() > AMgr.options.AlwaysInlineSize)
++StackDepth;
}
LCtx = LCtx->getParent();
@@ -406,9 +406,8 @@ namespace {
};
} // end anonymous namespace
-REGISTER_TRAIT_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap,
- CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *,
- unsigned))
+REGISTER_MAP_WITH_PROGRAMSTATE(DynamicDispatchBifurcationMap,
+ const MemRegion *, unsigned)
bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D,
NodeBuilder &Bldr, ExplodedNode *Pred,
@@ -505,6 +504,50 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred,
*this);
}
+ProgramStateRef ExprEngine::finishArgumentConstruction(ProgramStateRef State,
+ const CallEvent &Call) {
+ const Expr *E = Call.getOriginExpr();
+ // FIXME: Constructors to placement arguments of operator new
+ // are not supported yet.
+ if (!E || isa<CXXNewExpr>(E))
+ return State;
+
+ const LocationContext *LC = Call.getLocationContext();
+ for (unsigned CallI = 0, CallN = Call.getNumArgs(); CallI != CallN; ++CallI) {
+ unsigned I = Call.getASTArgumentIndex(CallI);
+ if (Optional<SVal> V =
+ getObjectUnderConstruction(State, {E, I}, LC)) {
+ SVal VV = *V;
+ (void)VV;
+ assert(cast<VarRegion>(VV.castAs<loc::MemRegionVal>().getRegion())
+ ->getStackFrame()->getParent()
+ ->getStackFrame() == LC->getStackFrame());
+ State = finishObjectConstruction(State, {E, I}, LC);
+ }
+ }
+
+ return State;
+}
+
+void ExprEngine::finishArgumentConstruction(ExplodedNodeSet &Dst,
+ ExplodedNode *Pred,
+ const CallEvent &Call) {
+ ProgramStateRef State = Pred->getState();
+ ProgramStateRef CleanedState = finishArgumentConstruction(State, Call);
+ if (CleanedState == State) {
+ Dst.insert(Pred);
+ return;
+ }
+
+ const Expr *E = Call.getOriginExpr();
+ const LocationContext *LC = Call.getLocationContext();
+ NodeBuilder B(Pred, Dst, *currBldrCtx);
+ static SimpleProgramPointTag Tag("ExprEngine",
+ "Finish argument construction");
+ PreStmt PP(E, LC, &Tag);
+ B.generateNode(PP, CleanedState, Pred);
+}
+
void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
const CallEvent &Call) {
// WARNING: At this time, the state attached to 'Call' may be older than the
@@ -516,7 +559,8 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
// Run any pre-call checks using the generic call interface.
ExplodedNodeSet dstPreVisit;
- getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred, Call, *this);
+ getCheckerManager().runCheckersForPreCall(dstPreVisit, Pred,
+ Call, *this);
// Actually evaluate the function call. We try each of the checkers
// to see if the can evaluate the function call, and get a callback at
@@ -525,8 +569,14 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred,
getCheckerManager().runCheckersForEvalCall(dstCallEvaluated, dstPreVisit,
Call, *this);
+ // If there were other constructors called for object-type arguments
+ // of this call, clean them up.
+ ExplodedNodeSet dstArgumentCleanup;
+ for (auto I : dstCallEvaluated)
+ finishArgumentConstruction(dstArgumentCleanup, I, Call);
+
// Finally, run any post-call checks.
- getCheckerManager().runCheckersForPostCall(Dst, dstCallEvaluated,
+ getCheckerManager().runCheckersForPostCall(Dst, dstArgumentCleanup,
Call, *this);
}
@@ -633,7 +683,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
: nullptr;
if (CC && isa<NewAllocatedObjectConstructionContext>(CC) &&
- !Opts.mayInlineCXXAllocator())
+ !Opts.MayInlineCXXAllocator)
return CIP_DisallowedOnce;
// FIXME: We don't handle constructors or destructors for arrays properly.
@@ -662,7 +712,7 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
// If we don't handle temporary destructors, we shouldn't inline
// their constructors.
if (CallOpts.IsTemporaryCtorOrDtor &&
- !Opts.includeTemporaryDtorsInCFG())
+ !Opts.ShouldIncludeTemporaryDtorsInCFG)
return CIP_DisallowedOnce;
// If we did not find the correct this-region, it would be pointless
@@ -693,7 +743,8 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
return CIP_DisallowedOnce;
// Allow disabling temporary destructor inlining with a separate option.
- if (CallOpts.IsTemporaryCtorOrDtor && !Opts.mayInlineCXXTemporaryDtors())
+ if (CallOpts.IsTemporaryCtorOrDtor &&
+ !Opts.MayInlineCXXTemporaryDtors)
return CIP_DisallowedOnce;
// If we did not find the correct this-region, it would be pointless
@@ -704,13 +755,13 @@ ExprEngine::mayInlineCallKind(const CallEvent &Call, const ExplodedNode *Pred,
break;
}
case CE_CXXAllocator:
- if (Opts.mayInlineCXXAllocator())
+ if (Opts.MayInlineCXXAllocator)
break;
// Do not inline allocators until we model deallocators.
// This is unfortunate, but basically necessary for smart pointers and such.
return CIP_DisallowedAlways;
case CE_ObjCMessage:
- if (!Opts.mayInlineObjCMethod())
+ if (!Opts.MayInlineObjCMethod)
return CIP_DisallowedAlways;
if (!(Opts.getIPAMode() == IPAK_DynamicDispatch ||
Opts.getIPAMode() == IPAK_DynamicDispatchBifurcate))
@@ -794,19 +845,19 @@ static bool mayInlineDecl(AnalysisManager &AMgr,
if (Ctx.getLangOpts().CPlusPlus) {
if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CalleeADC->getDecl())) {
// Conditionally control the inlining of template functions.
- if (!Opts.mayInlineTemplateFunctions())
+ if (!Opts.MayInlineTemplateFunctions)
if (FD->getTemplatedKind() != FunctionDecl::TK_NonTemplate)
return false;
// Conditionally control the inlining of C++ standard library functions.
- if (!Opts.mayInlineCXXStandardLibrary())
+ if (!Opts.MayInlineCXXStandardLibrary)
if (Ctx.getSourceManager().isInSystemHeader(FD->getLocation()))
if (AnalysisDeclContext::isInStdNamespace(FD))
return false;
// Conditionally control the inlining of methods on objects that look
// like C++ containers.
- if (!Opts.mayInlineCXXContainerMethods())
+ if (!Opts.MayInlineCXXContainerMethods)
if (!AMgr.isInCodeFile(FD->getLocation()))
if (isContainerMethod(Ctx, FD))
return false;
@@ -815,7 +866,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr,
// We don't currently do a good job modeling shared_ptr because we can't
// see the reference count, so treating as opaque is probably the best
// idea.
- if (!Opts.mayInlineCXXSharedPtrDtor())
+ if (!Opts.MayInlineCXXSharedPtrDtor)
if (isCXXSharedPtrDtor(FD))
return false;
}
@@ -828,7 +879,7 @@ static bool mayInlineDecl(AnalysisManager &AMgr,
return false;
// Do not inline large functions.
- if (CalleeCFG->getNumBlockIDs() > Opts.getMaxInlinableSize())
+ if (CalleeCFG->getNumBlockIDs() > Opts.MaxInlinableSize)
return false;
// It is possible that the live variables analysis cannot be
@@ -896,21 +947,21 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D,
unsigned StackDepth = 0;
examineStackFrames(D, Pred->getLocationContext(), IsRecursive, StackDepth);
if ((StackDepth >= Opts.InlineMaxStackDepth) &&
- ((CalleeCFG->getNumBlockIDs() > Opts.getAlwaysInlineSize())
+ ((CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize)
|| IsRecursive))
return false;
// Do not inline large functions too many times.
if ((Engine.FunctionSummaries->getNumTimesInlined(D) >
- Opts.getMaxTimesInlineLarge()) &&
+ Opts.MaxTimesInlineLarge) &&
CalleeCFG->getNumBlockIDs() >=
- Opts.getMinCFGSizeTreatFunctionsAsLarge()) {
+ Opts.MinCFGSizeTreatFunctionsAsLarge) {
NumReachedInlineCountMax++;
return false;
}
if (HowToInline == Inline_Minimal &&
- (CalleeCFG->getNumBlockIDs() > Opts.getAlwaysInlineSize()
+ (CalleeCFG->getNumBlockIDs() > Opts.AlwaysInlineSize
|| IsRecursive))
return false;
diff --git a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
index d76b9cbcfaca..6b8402f621e0 100644
--- a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
+++ b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp
@@ -129,7 +129,7 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S,
bool isContainerNull = state->isNull(collectionV).isConstrainedTrue();
ExplodedNodeSet dstLocation;
- evalLocation(dstLocation, S, elem, Pred, state, elementV, nullptr, false);
+ evalLocation(dstLocation, S, elem, Pred, state, elementV, false);
ExplodedNodeSet Tmp;
StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx);
@@ -197,7 +197,8 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
// Receiver is definitely nil, so run ObjCMessageNil callbacks and return.
if (nilState && !notNilState) {
- StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx);
+ ExplodedNodeSet dstNil;
+ StmtNodeBuilder Bldr(Pred, dstNil, *currBldrCtx);
bool HasTag = Pred->getLocation().getTag();
Pred = Bldr.generateNode(ME, Pred, nilState, nullptr,
ProgramPoint::PreStmtKind);
@@ -205,8 +206,12 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
(void)HasTag;
if (!Pred)
return;
- getCheckerManager().runCheckersForObjCMessageNil(Dst, Pred,
+
+ ExplodedNodeSet dstPostCheckers;
+ getCheckerManager().runCheckersForObjCMessageNil(dstPostCheckers, Pred,
*Msg, *this);
+ for (auto I : dstPostCheckers)
+ finishArgumentConstruction(Dst, I, *Msg);
return;
}
@@ -267,8 +272,13 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME,
defaultEvalCall(Bldr, Pred, *UpdatedMsg);
}
+ // If there were constructors called for object-type arguments, clean them up.
+ ExplodedNodeSet dstArgCleanup;
+ for (auto I : dstEval)
+ finishArgumentConstruction(dstArgCleanup, I, *Msg);
+
ExplodedNodeSet dstPostvisit;
- getCheckerManager().runCheckersForPostCall(dstPostvisit, dstEval,
+ getCheckerManager().runCheckersForPostCall(dstPostvisit, dstArgCleanup,
*Msg, *this);
// Finally, perform the post-condition check of the ObjCMessageExpr and store
diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
index d5e5f96dee0f..fc82f1176942 100644
--- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp
@@ -112,15 +112,24 @@ public:
FileID FID, const FileEntry *Entry, const char *declName);
// Rewrite the file specified by FID with HTML formatting.
- void RewriteFile(Rewriter &R, const SourceManager& SMgr,
- const PathPieces& path, FileID FID);
+ void RewriteFile(Rewriter &R, const PathPieces& path, FileID FID);
- /// \return Javascript for navigating the HTML report using j/k keys.
- std::string generateKeyboardNavigationJavascript();
private:
/// \return Javascript for displaying shortcuts help;
- std::string showHelpJavascript();
+ StringRef showHelpJavascript();
+
+ /// \return Javascript for navigating the HTML report using j/k keys.
+ StringRef generateKeyboardNavigationJavascript();
+
+ /// \return JavaScript for an option to only show relevant lines.
+ std::string showRelevantLinesJavascript(
+ const PathDiagnostic &D, const PathPieces &path);
+
+ /// Write executed lines from \p D in JSON format into \p os.
+ void dumpCoverageData(const PathDiagnostic &D,
+ const PathPieces &path,
+ llvm::raw_string_ostream &os);
};
} // namespace
@@ -194,7 +203,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
FullSourceLoc L(
SMgr.getExpansionLoc(path.back()->getLocation().asLocation()),
SMgr);
- FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr);
+ FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getBeginLoc()), SMgr);
offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber();
}
}
@@ -209,7 +218,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D,
int FD;
SmallString<128> Model, ResultPath;
- if (!AnalyzerOpts.shouldWriteStableReportFilename()) {
+ if (!AnalyzerOpts.ShouldWriteStableReportFilename) {
llvm::sys::path::append(Model, Directory, "report-%%%%%%.html");
if (std::error_code EC =
llvm::sys::fs::make_absolute(Model)) {
@@ -269,7 +278,7 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
continue;
FileIDs.push_back(FID);
- RewriteFile(R, SMgr, path, FID);
+ RewriteFile(R, path, FID);
}
if (SupportsCrossFileDiagnostics && FileIDs.size() > 1) {
@@ -332,28 +341,12 @@ std::string HTMLDiagnostics::GenerateHTML(const PathDiagnostic& D, Rewriter &R,
return os.str();
}
-/// Write executed lines from \p D in JSON format into \p os.
-static void serializeExecutedLines(
+void HTMLDiagnostics::dumpCoverageData(
const PathDiagnostic &D,
const PathPieces &path,
llvm::raw_string_ostream &os) {
- // Copy executed lines from path diagnostics.
- std::map<unsigned, std::set<unsigned>> ExecutedLines;
- for (auto I = D.executedLines_begin(),
- E = D.executedLines_end(); I != E; ++I) {
- std::set<unsigned> &LinesInFile = ExecutedLines[I->first];
- for (unsigned LineNo : I->second) {
- LinesInFile.insert(LineNo);
- }
- }
- // We need to include all lines for which any kind of diagnostics appears.
- for (const auto &P : path) {
- FullSourceLoc Loc = P->getLocation().asLocation().getExpansionLoc();
- FileID FID = Loc.getFileID();
- unsigned LineNo = Loc.getLineNumber();
- ExecutedLines[FID.getHashValue()].insert(LineNo);
- }
+ const FilesToLineNumsMap &ExecutedLines = D.getExecutedLines();
os << "var relevant_lines = {";
for (auto I = ExecutedLines.begin(),
@@ -361,7 +354,7 @@ static void serializeExecutedLines(
if (I != ExecutedLines.begin())
os << ", ";
- os << "\"" << I->first << "\": {";
+ os << "\"" << I->first.getHashValue() << "\": {";
for (unsigned LineNo : I->second) {
if (LineNo != *(I->second.begin()))
os << ", ";
@@ -374,13 +367,12 @@ static void serializeExecutedLines(
os << "};";
}
-/// \return JavaScript for an option to only show relevant lines.
-static std::string showRelevantLinesJavascript(
+std::string HTMLDiagnostics::showRelevantLinesJavascript(
const PathDiagnostic &D, const PathPieces &path) {
std::string s;
llvm::raw_string_ostream os(s);
os << "<script type='text/javascript'>\n";
- serializeExecutedLines(D, path, os);
+ dumpCoverageData(D, path, os);
os << R"<<<(
var filterCounterexample = function (hide) {
@@ -586,7 +578,7 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R,
html::AddHeaderFooterInternalBuiltinCSS(R, FID, Entry->getName());
}
-std::string HTMLDiagnostics::showHelpJavascript() {
+StringRef HTMLDiagnostics::showHelpJavascript() {
return R"<<<(
<script type='text/javascript'>
@@ -614,8 +606,8 @@ window.addEventListener("keydown", function (event) {
)<<<";
}
-void HTMLDiagnostics::RewriteFile(Rewriter &R, const SourceManager& SMgr,
- const PathPieces& path, FileID FID) {
+void HTMLDiagnostics::RewriteFile(Rewriter &R,
+ const PathPieces& path, FileID FID) {
// Process the path.
// Maintain the counts of extra note pieces separately.
unsigned TotalPieces = path.size();
@@ -944,7 +936,7 @@ void HTMLDiagnostics::HighlightRange(Rewriter& R, FileID BugFileID,
html::HighlightRange(R, InstantiationStart, E, HighlightStart, HighlightEnd);
}
-std::string HTMLDiagnostics::generateKeyboardNavigationJavascript() {
+StringRef HTMLDiagnostics::generateKeyboardNavigationJavascript() {
return R"<<<(
<script type='text/javascript'>
var digitMatcher = new RegExp("[0-9]+");
@@ -997,7 +989,8 @@ var numToId = function(num) {
};
var navigateTo = function(up) {
- var numItems = document.querySelectorAll(".line > .msg").length;
+ var numItems = document.querySelectorAll(
+ ".line > .msgEvent, .line > .msgControl").length;
var currentSelected = findNum();
var newSelected = move(currentSelected, up, numItems);
var newEl = numToId(newSelected, numItems);
diff --git a/lib/StaticAnalyzer/Core/LoopWidening.cpp b/lib/StaticAnalyzer/Core/LoopWidening.cpp
index 9192f49eac6d..8f6cb9a6b09e 100644
--- a/lib/StaticAnalyzer/Core/LoopWidening.cpp
+++ b/lib/StaticAnalyzer/Core/LoopWidening.cpp
@@ -81,11 +81,12 @@ ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState,
// 'this' pointer is not an lvalue, we should not invalidate it. If the loop
// is located in a method, constructor or destructor, the value of 'this'
- // pointer shoule remain unchanged.
- if (const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl())) {
- const CXXThisRegion *ThisR = MRMgr.getCXXThisRegion(
- CXXMD->getThisType(STC->getAnalysisDeclContext()->getASTContext()),
- STC);
+ // pointer should remain unchanged. Ignore static methods, since they do not
+ // have 'this' pointers.
+ const CXXMethodDecl *CXXMD = dyn_cast<CXXMethodDecl>(STC->getDecl());
+ if (CXXMD && !CXXMD->isStatic()) {
+ const CXXThisRegion *ThisR =
+ MRMgr.getCXXThisRegion(CXXMD->getThisType(), STC);
ITraits.setTrait(ThisR,
RegionAndSymbolInvalidationTraits::TK_PreserveContents);
}
diff --git a/lib/StaticAnalyzer/Core/MemRegion.cpp b/lib/StaticAnalyzer/Core/MemRegion.cpp
index cb2122c7749e..9a1d4d73c20b 100644
--- a/lib/StaticAnalyzer/Core/MemRegion.cpp
+++ b/lib/StaticAnalyzer/Core/MemRegion.cpp
@@ -225,6 +225,10 @@ QualType CXXBaseObjectRegion::getValueType() const {
return QualType(getDecl()->getTypeForDecl(), 0);
}
+QualType CXXDerivedObjectRegion::getValueType() const {
+ return QualType(getDecl()->getTypeForDecl(), 0);
+}
+
//===----------------------------------------------------------------------===//
// FoldingSet profiling.
//===----------------------------------------------------------------------===//
@@ -404,6 +408,17 @@ void CXXBaseObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const {
ProfileRegion(ID, getDecl(), isVirtual(), superRegion);
}
+void CXXDerivedObjectRegion::ProfileRegion(llvm::FoldingSetNodeID &ID,
+ const CXXRecordDecl *RD,
+ const MemRegion *SReg) {
+ ID.AddPointer(RD);
+ ID.AddPointer(SReg);
+}
+
+void CXXDerivedObjectRegion::Profile(llvm::FoldingSetNodeID &ID) const {
+ ProfileRegion(ID, getDecl(), superRegion);
+}
+
//===----------------------------------------------------------------------===//
// Region anchors.
//===----------------------------------------------------------------------===//
@@ -442,7 +457,7 @@ void MemRegion::dumpToStream(raw_ostream &os) const {
}
void AllocaRegion::dumpToStream(raw_ostream &os) const {
- os << "alloca{" << static_cast<const void *>(Ex) << ',' << Cnt << '}';
+ os << "alloca{S" << Ex->getID(getContext()) << ',' << Cnt << '}';
}
void FunctionCodeRegion::dumpToStream(raw_ostream &os) const {
@@ -466,16 +481,20 @@ void BlockDataRegion::dumpToStream(raw_ostream &os) const {
void CompoundLiteralRegion::dumpToStream(raw_ostream &os) const {
// FIXME: More elaborate pretty-printing.
- os << "{ " << static_cast<const void *>(CL) << " }";
+ os << "{ S" << CL->getID(getContext()) << " }";
}
void CXXTempObjectRegion::dumpToStream(raw_ostream &os) const {
- os << "temp_object{" << getValueType().getAsString() << ','
- << static_cast<const void *>(Ex) << '}';
+ os << "temp_object{" << getValueType().getAsString() << ", "
+ << "S" << Ex->getID(getContext()) << '}';
}
void CXXBaseObjectRegion::dumpToStream(raw_ostream &os) const {
- os << "base{" << superRegion << ',' << getDecl()->getName() << '}';
+ os << "Base{" << superRegion << ',' << getDecl()->getName() << '}';
+}
+
+void CXXDerivedObjectRegion::dumpToStream(raw_ostream &os) const {
+ os << "Derived{" << superRegion << ',' << getDecl()->getName() << '}';
}
void CXXThisRegion::dumpToStream(raw_ostream &os) const {
@@ -483,7 +502,7 @@ void CXXThisRegion::dumpToStream(raw_ostream &os) const {
}
void ElementRegion::dumpToStream(raw_ostream &os) const {
- os << "element{" << superRegion << ','
+ os << "Element{" << superRegion << ','
<< Index << ',' << getElementType().getAsString() << '}';
}
@@ -492,7 +511,7 @@ void FieldRegion::dumpToStream(raw_ostream &os) const {
}
void ObjCIvarRegion::dumpToStream(raw_ostream &os) const {
- os << "ivar{" << superRegion << ',' << *getDecl() << '}';
+ os << "Ivar{" << superRegion << ',' << *getDecl() << '}';
}
void StringRegion::dumpToStream(raw_ostream &os) const {
@@ -516,7 +535,7 @@ void VarRegion::dumpToStream(raw_ostream &os) const {
if (const IdentifierInfo *ID = VD->getIdentifier())
os << ID->getName();
else
- os << "VarRegion{" << static_cast<const void *>(this) << '}';
+ os << "VarRegion{D" << VD->getID() << '}';
}
LLVM_DUMP_METHOD void RegionRawOffset::dump() const {
@@ -578,7 +597,7 @@ void MemRegion::printPretty(raw_ostream &os) const {
os << "'";
}
-void MemRegion::printPrettyAsExpr(raw_ostream &os) const {
+void MemRegion::printPrettyAsExpr(raw_ostream &) const {
llvm_unreachable("This region cannot be printed pretty.");
}
@@ -630,6 +649,14 @@ void CXXBaseObjectRegion::printPrettyAsExpr(raw_ostream &os) const {
superRegion->printPrettyAsExpr(os);
}
+bool CXXDerivedObjectRegion::canPrintPrettyAsExpr() const {
+ return superRegion->canPrintPrettyAsExpr();
+}
+
+void CXXDerivedObjectRegion::printPrettyAsExpr(raw_ostream &os) const {
+ superRegion->printPrettyAsExpr(os);
+}
+
std::string MemRegion::getDescriptiveName(bool UseQuotes) const {
std::string VariableName;
std::string ArrayIndices;
@@ -1061,6 +1088,12 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD,
return getSubRegion<CXXBaseObjectRegion>(RD, IsVirtual, Super);
}
+const CXXDerivedObjectRegion *
+MemRegionManager::getCXXDerivedObjectRegion(const CXXRecordDecl *RD,
+ const SubRegion *Super) {
+ return getSubRegion<CXXDerivedObjectRegion>(RD, Super);
+}
+
const CXXThisRegion*
MemRegionManager::getCXXThisRegion(QualType thisPointerTy,
const LocationContext *LC) {
@@ -1072,9 +1105,8 @@ MemRegionManager::getCXXThisRegion(QualType thisPointerTy,
// FIXME: when operator() of lambda is analyzed as a top level function and
// 'this' refers to a this to the enclosing scope, there is no right region to
// return.
- while (!LC->inTopFrame() &&
- (!D || D->isStatic() ||
- PT != D->getThisType(getContext())->getAs<PointerType>())) {
+ while (!LC->inTopFrame() && (!D || D->isStatic() ||
+ PT != D->getThisType()->getAs<PointerType>())) {
LC = LC->getParent();
D = dyn_cast<CXXMethodDecl>(LC->getDecl());
}
@@ -1131,6 +1163,7 @@ const MemRegion *MemRegion::getBaseRegion() const {
case MemRegion::FieldRegionKind:
case MemRegion::ObjCIvarRegionKind:
case MemRegion::CXXBaseObjectRegionKind:
+ case MemRegion::CXXDerivedObjectRegionKind:
R = cast<SubRegion>(R)->getSuperRegion();
continue;
default:
@@ -1141,7 +1174,16 @@ const MemRegion *MemRegion::getBaseRegion() const {
return R;
}
-bool MemRegion::isSubRegionOf(const MemRegion *R) const {
+// getgetMostDerivedObjectRegion gets the region of the root class of a C++
+// class hierarchy.
+const MemRegion *MemRegion::getMostDerivedObjectRegion() const {
+ const MemRegion *R = this;
+ while (const auto *BR = dyn_cast<CXXBaseObjectRegion>(R))
+ R = BR->getSuperRegion();
+ return R;
+}
+
+bool MemRegion::isSubRegionOf(const MemRegion *) const {
return false;
}
@@ -1149,7 +1191,7 @@ bool MemRegion::isSubRegionOf(const MemRegion *R) const {
// View handling.
//===----------------------------------------------------------------------===//
-const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const {
+const MemRegion *MemRegion::StripCasts(bool StripBaseAndDerivedCasts) const {
const MemRegion *R = this;
while (true) {
switch (R->getKind()) {
@@ -1161,9 +1203,10 @@ const MemRegion *MemRegion::StripCasts(bool StripBaseCasts) const {
break;
}
case CXXBaseObjectRegionKind:
- if (!StripBaseCasts)
+ case CXXDerivedObjectRegionKind:
+ if (!StripBaseAndDerivedCasts)
return R;
- R = cast<CXXBaseObjectRegion>(R)->getSuperRegion();
+ R = cast<TypedValueRegion>(R)->getSuperRegion();
break;
default:
return R;
@@ -1344,6 +1387,12 @@ static RegionOffset calculateOffset(const MemRegion *R) {
Offset += BaseOffset.getQuantity() * R->getContext().getCharWidth();
break;
}
+
+ case MemRegion::CXXDerivedObjectRegionKind: {
+ // TODO: Store the base type in the CXXDerivedObjectRegion and use it.
+ goto Finish;
+ }
+
case MemRegion::ElementRegionKind: {
const auto *ER = cast<ElementRegion>(R);
R = ER->getSuperRegion();
diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
index 1b698ec5c086..3e93bb6a7c4f 100644
--- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
+++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp
@@ -536,7 +536,7 @@ PathDiagnosticConsumer::FilesMade::getFiles(const PathDiagnostic &PD) {
static SourceLocation getValidSourceLocation(const Stmt* S,
LocationOrAnalysisDeclContext LAC,
bool UseEnd = false) {
- SourceLocation L = UseEnd ? S->getLocEnd() : S->getLocStart();
+ SourceLocation L = UseEnd ? S->getEndLoc() : S->getBeginLoc();
assert(!LAC.isNull() && "A valid LocationContext or AnalysisDeclContext should "
"be passed to PathDiagnosticLocation upon creation.");
@@ -562,13 +562,13 @@ static SourceLocation getValidSourceLocation(const Stmt* S,
if (!Parent) {
const Stmt *Body = ADC->getBody();
if (Body)
- L = Body->getLocStart();
+ L = Body->getBeginLoc();
else
- L = ADC->getDecl()->getLocEnd();
+ L = ADC->getDecl()->getEndLoc();
break;
}
- L = UseEnd ? Parent->getLocEnd() : Parent->getLocStart();
+ L = UseEnd ? Parent->getEndLoc() : Parent->getBeginLoc();
} while (!L.isValid());
}
@@ -635,7 +635,7 @@ getLocationForCaller(const StackFrameContext *SFC,
PathDiagnosticLocation
PathDiagnosticLocation::createBegin(const Decl *D,
const SourceManager &SM) {
- return PathDiagnosticLocation(D->getLocStart(), SM, SingleLocK);
+ return PathDiagnosticLocation(D->getBeginLoc(), SM, SingleLocK);
}
PathDiagnosticLocation
@@ -695,7 +695,7 @@ PathDiagnosticLocation::createDeclBegin(const LocationContext *LC,
// FIXME: Should handle CXXTryStmt if analyser starts supporting C++.
if (const auto *CS = dyn_cast_or_null<CompoundStmt>(LC->getDecl()->getBody()))
if (!CS->body_empty()) {
- SourceLocation Loc = (*CS->body_begin())->getLocStart();
+ SourceLocation Loc = (*CS->body_begin())->getBeginLoc();
return PathDiagnosticLocation(Loc, SM, SingleLocK);
}
@@ -723,6 +723,8 @@ PathDiagnosticLocation::create(const ProgramPoint& P,
} else if (Optional<PostInitializer> PIP = P.getAs<PostInitializer>()) {
return PathDiagnosticLocation(PIP->getInitializer()->getSourceLocation(),
SMng);
+ } else if (Optional<PreImplicitCall> PIC = P.getAs<PreImplicitCall>()) {
+ return PathDiagnosticLocation(PIC->getLocation(), SMng);
} else if (Optional<PostImplicitCall> PIE = P.getAs<PostImplicitCall>()) {
return PathDiagnosticLocation(PIE->getLocation(), SMng);
} else if (Optional<CallEnter> CE = P.getAs<CallEnter>()) {
@@ -736,10 +738,10 @@ PathDiagnosticLocation::create(const ProgramPoint& P,
} else if (Optional<BlockEntrance> BE = P.getAs<BlockEntrance>()) {
CFGElement BlockFront = BE->getBlock()->front();
if (auto StmtElt = BlockFront.getAs<CFGStmt>()) {
- return PathDiagnosticLocation(StmtElt->getStmt()->getLocStart(), SMng);
+ return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng);
} else if (auto NewAllocElt = BlockFront.getAs<CFGNewAllocator>()) {
return PathDiagnosticLocation(
- NewAllocElt->getAllocatorExpr()->getLocStart(), SMng);
+ NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng);
}
llvm_unreachable("Unexpected CFG element at front of block");
} else {
@@ -774,18 +776,20 @@ const Stmt *PathDiagnosticLocation::getStmt(const ExplodedNode *N) {
}
// Otherwise, see if the node's program point directly points to a statement.
ProgramPoint P = N->getLocation();
- if (Optional<StmtPoint> SP = P.getAs<StmtPoint>())
+ if (auto SP = P.getAs<StmtPoint>())
return SP->getStmt();
- if (Optional<BlockEdge> BE = P.getAs<BlockEdge>())
+ if (auto BE = P.getAs<BlockEdge>())
return BE->getSrc()->getTerminator();
- if (Optional<CallEnter> CE = P.getAs<CallEnter>())
+ if (auto CE = P.getAs<CallEnter>())
return CE->getCallExpr();
- if (Optional<CallExitEnd> CEE = P.getAs<CallExitEnd>())
+ if (auto CEE = P.getAs<CallExitEnd>())
return CEE->getCalleeContext()->getCallSite();
- if (Optional<PostInitializer> PIPP = P.getAs<PostInitializer>())
+ if (auto PIPP = P.getAs<PostInitializer>())
return PIPP->getInitializer()->getInit();
- if (Optional<CallExitBegin> CEB = P.getAs<CallExitBegin>())
+ if (auto CEB = P.getAs<CallExitBegin>())
return CEB->getReturnStmt();
+ if (auto FEP = P.getAs<FunctionExitPoint>())
+ return FEP->getStmt();
return nullptr;
}
@@ -822,17 +826,21 @@ PathDiagnosticLocation
const SourceManager &SM) {
assert(N && "Cannot create a location with a null node.");
const Stmt *S = getStmt(N);
+ const LocationContext *LC = N->getLocationContext();
if (!S) {
// If this is an implicit call, return the implicit call point location.
if (Optional<PreImplicitCall> PIE = N->getLocationAs<PreImplicitCall>())
return PathDiagnosticLocation(PIE->getLocation(), SM);
+ if (auto FE = N->getLocationAs<FunctionExitPoint>()) {
+ if (const ReturnStmt *RS = FE->getStmt())
+ return PathDiagnosticLocation::createBegin(RS, SM, LC);
+ }
S = getNextStmt(N);
}
if (S) {
ProgramPoint P = N->getLocation();
- const LocationContext *LC = N->getLocationContext();
// For member expressions, return the location of the '.' or '->'.
if (const auto *ME = dyn_cast<MemberExpr>(S))
@@ -845,7 +853,7 @@ PathDiagnosticLocation
if (P.getAs<PostStmtPurgeDeadSymbols>())
return PathDiagnosticLocation::createEnd(S, SM, LC);
- if (S->getLocStart().isValid())
+ if (S->getBeginLoc().isValid())
return PathDiagnosticLocation(S, SM, LC);
return PathDiagnosticLocation(getValidSourceLocation(S, LC), SM);
}
@@ -904,7 +912,7 @@ PathDiagnosticRange
const auto *DS = cast<DeclStmt>(S);
if (DS->isSingleDecl()) {
// Should always be the case, but we'll be defensive.
- return SourceRange(DS->getLocStart(),
+ return SourceRange(DS->getBeginLoc(),
DS->getSingleDecl()->getLocation());
}
break;
@@ -964,7 +972,7 @@ void PathDiagnosticLocation::flatten() {
//===----------------------------------------------------------------------===//
std::shared_ptr<PathDiagnosticCallPiece>
-PathDiagnosticCallPiece::construct(const ExplodedNode *N, const CallExitEnd &CE,
+PathDiagnosticCallPiece::construct(const CallExitEnd &CE,
const SourceManager &SM) {
const Decl *caller = CE.getLocationContext()->getDecl();
PathDiagnosticLocation pos = getLocationForCaller(CE.getCalleeContext(),
diff --git a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
index cfe780db9ec9..db4cf76578d8 100644
--- a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
+++ b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp
@@ -16,6 +16,7 @@
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Version.h"
#include "clang/Lex/Preprocessor.h"
+#include "clang/Lex/TokenConcatenation.h"
#include "clang/Rewrite/Core/HTMLRewrite.h"
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
@@ -24,20 +25,26 @@
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Support/Casting.h"
+
using namespace clang;
using namespace ento;
using namespace markup;
+//===----------------------------------------------------------------------===//
+// Declarations of helper classes and functions for emitting bug reports in
+// plist format.
+//===----------------------------------------------------------------------===//
+
namespace {
class PlistDiagnostics : public PathDiagnosticConsumer {
const std::string OutputFile;
- const LangOptions &LangOpts;
+ const Preprocessor &PP;
+ AnalyzerOptions &AnOpts;
const bool SupportsCrossFileDiagnostics;
- const bool SerializeStatistics;
public:
PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
const std::string& prefix,
- const LangOptions &LangOpts,
+ const Preprocessor &PP,
bool supportsMultipleFiles);
~PlistDiagnostics() override {}
@@ -59,37 +66,116 @@ namespace {
};
} // end anonymous namespace
-PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
- const std::string& output,
- const LangOptions &LO,
- bool supportsMultipleFiles)
- : OutputFile(output),
- LangOpts(LO),
- SupportsCrossFileDiagnostics(supportsMultipleFiles),
- SerializeStatistics(AnalyzerOpts.shouldSerializeStats()) {}
+namespace {
-void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
- PathDiagnosticConsumers &C,
- const std::string& s,
- const Preprocessor &PP) {
- C.push_back(new PlistDiagnostics(AnalyzerOpts, s,
- PP.getLangOpts(), false));
-}
+/// A helper class for emitting a single report.
+class PlistPrinter {
+ const FIDMap& FM;
+ AnalyzerOptions &AnOpts;
+ const Preprocessor &PP;
+ llvm::SmallVector<const PathDiagnosticMacroPiece *, 0> MacroPieces;
+
+public:
+ PlistPrinter(const FIDMap& FM, AnalyzerOptions &AnOpts,
+ const Preprocessor &PP)
+ : FM(FM), AnOpts(AnOpts), PP(PP) {
+ }
-void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
- PathDiagnosticConsumers &C,
- const std::string &s,
- const Preprocessor &PP) {
- C.push_back(new PlistDiagnostics(AnalyzerOpts, s,
- PP.getLangOpts(), true));
-}
+ void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P) {
+ ReportPiece(o, P, /*indent*/ 4, /*depth*/ 0, /*includeControlFlow*/ true);
+
+ // Don't emit a warning about an unused private field.
+ (void)AnOpts;
+ }
+
+ /// Print the expansions of the collected macro pieces.
+ ///
+ /// Each time ReportDiag is called on a PathDiagnosticMacroPiece (or, if one
+ /// is found through a call piece, etc), it's subpieces are reported, and the
+ /// piece itself is collected. Call this function after the entire bugpath
+ /// was reported.
+ void ReportMacroExpansions(raw_ostream &o, unsigned indent);
+
+private:
+ void ReportPiece(raw_ostream &o, const PathDiagnosticPiece &P,
+ unsigned indent, unsigned depth, bool includeControlFlow,
+ bool isKeyEvent = false) {
+ switch (P.getKind()) {
+ case PathDiagnosticPiece::ControlFlow:
+ if (includeControlFlow)
+ ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), indent);
+ break;
+ case PathDiagnosticPiece::Call:
+ ReportCall(o, cast<PathDiagnosticCallPiece>(P), indent,
+ depth);
+ break;
+ case PathDiagnosticPiece::Event:
+ ReportEvent(o, cast<PathDiagnosticEventPiece>(P), indent, depth,
+ isKeyEvent);
+ break;
+ case PathDiagnosticPiece::Macro:
+ ReportMacroSubPieces(o, cast<PathDiagnosticMacroPiece>(P), indent,
+ depth);
+ break;
+ case PathDiagnosticPiece::Note:
+ ReportNote(o, cast<PathDiagnosticNotePiece>(P), indent);
+ break;
+ }
+ }
+
+ void EmitRanges(raw_ostream &o, const ArrayRef<SourceRange> Ranges,
+ unsigned indent);
+ void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent);
+
+ void ReportControlFlow(raw_ostream &o,
+ const PathDiagnosticControlFlowPiece& P,
+ unsigned indent);
+ void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
+ unsigned indent, unsigned depth, bool isKeyEvent = false);
+ void ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P,
+ unsigned indent, unsigned depth);
+ void ReportMacroSubPieces(raw_ostream &o, const PathDiagnosticMacroPiece& P,
+ unsigned indent, unsigned depth);
+ void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
+ unsigned indent);
+};
+
+} // end of anonymous namespace
-static void EmitRanges(raw_ostream &o,
- const ArrayRef<SourceRange> Ranges,
- const FIDMap& FM,
- const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent) {
+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 void printBugPath(llvm::raw_ostream &o, const FIDMap& FM,
+ AnalyzerOptions &AnOpts,
+ const Preprocessor &PP,
+ const PathPieces &Path);
+
+/// Print coverage information to output stream {@code o}.
+/// May modify the used list of files {@code Fids} by inserting new ones.
+static void printCoverage(const PathDiagnostic *D,
+ unsigned InputIndentLevel,
+ SmallVectorImpl<FileID> &Fids,
+ FIDMap &FM,
+ llvm::raw_fd_ostream &o);
+
+static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc,
+ const Preprocessor &PP);
+
+//===----------------------------------------------------------------------===//
+// Methods of PlistPrinter.
+//===----------------------------------------------------------------------===//
+
+void PlistPrinter::EmitRanges(raw_ostream &o,
+ const ArrayRef<SourceRange> Ranges,
+ unsigned indent) {
if (Ranges.empty())
return;
@@ -97,6 +183,10 @@ static void EmitRanges(raw_ostream &o,
Indent(o, indent) << "<key>ranges</key>\n";
Indent(o, indent) << "<array>\n";
++indent;
+
+ const SourceManager &SM = PP.getSourceManager();
+ const LangOptions &LangOpts = PP.getLangOpts();
+
for (auto &R : Ranges)
EmitRange(o, SM,
Lexer::getAsCharRange(SM.getExpansionRange(R), SM, LangOpts),
@@ -105,7 +195,8 @@ static void EmitRanges(raw_ostream &o,
Indent(o, indent) << "</array>\n";
}
-static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) {
+void PlistPrinter::EmitMessage(raw_ostream &o, StringRef Message,
+ unsigned indent) {
// Output the text.
assert(!Message.empty());
Indent(o, indent) << "<key>extended_message</key>\n";
@@ -119,12 +210,12 @@ static void EmitMessage(raw_ostream &o, StringRef Message, unsigned indent) {
EmitString(o, Message) << '\n';
}
-static void ReportControlFlow(raw_ostream &o,
- const PathDiagnosticControlFlowPiece& P,
- const FIDMap& FM,
- const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent) {
+void PlistPrinter::ReportControlFlow(raw_ostream &o,
+ const PathDiagnosticControlFlowPiece& P,
+ unsigned indent) {
+
+ const SourceManager &SM = PP.getSourceManager();
+ const LangOptions &LangOpts = PP.getLangOpts();
Indent(o, indent) << "<dict>\n";
++indent;
@@ -173,13 +264,11 @@ static void ReportControlFlow(raw_ostream &o,
Indent(o, indent) << "</dict>\n";
}
-static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
- const FIDMap& FM,
- const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth,
- bool isKeyEvent = false) {
+void PlistPrinter::ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
+ unsigned indent, unsigned depth,
+ bool isKeyEvent) {
+
+ const SourceManager &SM = PP.getSourceManager();
Indent(o, indent) << "<dict>\n";
++indent;
@@ -198,7 +287,7 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
// Output the ranges (if any).
ArrayRef<SourceRange> Ranges = P.getRanges();
- EmitRanges(o, Ranges, FM, SM, LangOpts, indent);
+ EmitRanges(o, Ranges, indent);
// Output the call depth.
Indent(o, indent) << "<key>depth</key>";
@@ -212,61 +301,80 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticEventPiece& P,
Indent(o, indent); o << "</dict>\n";
}
-static void ReportPiece(raw_ostream &o,
- const PathDiagnosticPiece &P,
- const FIDMap& FM, const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth,
- bool includeControlFlow,
- bool isKeyEvent = false);
-
-static void ReportCall(raw_ostream &o,
- const PathDiagnosticCallPiece &P,
- const FIDMap& FM, const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth) {
+void PlistPrinter::ReportCall(raw_ostream &o, const PathDiagnosticCallPiece &P,
+ unsigned indent,
+ unsigned depth) {
if (auto callEnter = P.getCallEnterEvent())
- ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true,
+ ReportPiece(o, *callEnter, indent, depth, /*includeControlFlow*/ true,
P.isLastInMainSourceFile());
++depth;
if (auto callEnterWithinCaller = P.getCallEnterWithinCallerEvent())
- ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts,
- indent, depth, true);
+ ReportPiece(o, *callEnterWithinCaller, indent, depth,
+ /*includeControlFlow*/ true);
for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I)
- ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true);
+ ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ true);
--depth;
if (auto callExit = P.getCallExitEvent())
- ReportPiece(o, *callExit, FM, SM, LangOpts, indent, depth, true);
+ ReportPiece(o, *callExit, indent, depth, /*includeControlFlow*/ true);
}
-static void ReportMacro(raw_ostream &o,
- const PathDiagnosticMacroPiece& P,
- const FIDMap& FM, const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth) {
+void PlistPrinter::ReportMacroSubPieces(raw_ostream &o,
+ const PathDiagnosticMacroPiece& P,
+ unsigned indent, unsigned depth) {
+ MacroPieces.push_back(&P);
- for (PathPieces::const_iterator I = P.subPieces.begin(), E=P.subPieces.end();
- I!=E; ++I) {
- ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, false);
+ for (PathPieces::const_iterator I = P.subPieces.begin(),
+ E = P.subPieces.end();
+ I != E; ++I) {
+ ReportPiece(o, **I, indent, depth, /*includeControlFlow*/ false);
+ }
+}
+
+void PlistPrinter::ReportMacroExpansions(raw_ostream &o, unsigned indent) {
+
+ for (const PathDiagnosticMacroPiece *P : MacroPieces) {
+ const SourceManager &SM = PP.getSourceManager();
+ ExpansionInfo EI = getExpandedMacro(P->getLocation().asLocation(), PP);
+
+ Indent(o, indent) << "<dict>\n";
+ ++indent;
+
+ // Output the location.
+ FullSourceLoc L = P->getLocation().asLocation();
+
+ Indent(o, indent) << "<key>location</key>\n";
+ EmitLocation(o, SM, L, FM, indent);
+
+ // Output the ranges (if any).
+ ArrayRef<SourceRange> Ranges = P->getRanges();
+ EmitRanges(o, Ranges, indent);
+
+ // Output the macro name.
+ Indent(o, indent) << "<key>name</key>";
+ EmitString(o, EI.MacroName) << '\n';
+
+ // Output what it expands into.
+ Indent(o, indent) << "<key>expansion</key>";
+ EmitString(o, EI.Expansion) << '\n';
+
+ // Finish up.
+ --indent;
+ Indent(o, indent);
+ o << "</dict>\n";
}
}
-static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
- const FIDMap& FM,
- const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth) {
+void PlistPrinter::ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
+ unsigned indent) {
+
+ const SourceManager &SM = PP.getSourceManager();
Indent(o, indent) << "<dict>\n";
++indent;
@@ -279,7 +387,7 @@ static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
// Output the ranges (if any).
ArrayRef<SourceRange> Ranges = P.getRanges();
- EmitRanges(o, Ranges, FM, SM, LangOpts, indent);
+ EmitRanges(o, Ranges, indent);
// Output the text.
EmitMessage(o, P.getString(), indent);
@@ -289,45 +397,115 @@ static void ReportNote(raw_ostream &o, const PathDiagnosticNotePiece& P,
Indent(o, indent); o << "</dict>\n";
}
-static void ReportDiag(raw_ostream &o, const PathDiagnosticPiece& P,
- const FIDMap& FM, const SourceManager &SM,
- const LangOptions &LangOpts) {
- ReportPiece(o, P, FM, SM, LangOpts, 4, 0, true);
+//===----------------------------------------------------------------------===//
+// Static function definitions.
+//===----------------------------------------------------------------------===//
+
+/// Print coverage information to output stream {@code o}.
+/// May modify the used list of files {@code Fids} by inserting new ones.
+static void printCoverage(const PathDiagnostic *D,
+ unsigned InputIndentLevel,
+ SmallVectorImpl<FileID> &Fids,
+ FIDMap &FM,
+ llvm::raw_fd_ostream &o) {
+ unsigned IndentLevel = InputIndentLevel;
+
+ Indent(o, IndentLevel) << "<key>ExecutedLines</key>\n";
+ Indent(o, IndentLevel) << "<dict>\n";
+ IndentLevel++;
+
+ // Mapping from file IDs to executed lines.
+ const FilesToLineNumsMap &ExecutedLines = D->getExecutedLines();
+ for (auto I = ExecutedLines.begin(), E = ExecutedLines.end(); I != E; ++I) {
+ unsigned FileKey = AddFID(FM, Fids, I->first);
+ Indent(o, IndentLevel) << "<key>" << FileKey << "</key>\n";
+ Indent(o, IndentLevel) << "<array>\n";
+ IndentLevel++;
+ for (unsigned LineNo : I->second) {
+ Indent(o, IndentLevel);
+ EmitInteger(o, LineNo) << "\n";
+ }
+ IndentLevel--;
+ Indent(o, IndentLevel) << "</array>\n";
+ }
+ IndentLevel--;
+ Indent(o, IndentLevel) << "</dict>\n";
+
+ assert(IndentLevel == InputIndentLevel);
}
-static void ReportPiece(raw_ostream &o,
- const PathDiagnosticPiece &P,
- const FIDMap& FM, const SourceManager &SM,
- const LangOptions &LangOpts,
- unsigned indent,
- unsigned depth,
- bool includeControlFlow,
- bool isKeyEvent) {
- switch (P.getKind()) {
- case PathDiagnosticPiece::ControlFlow:
- if (includeControlFlow)
- ReportControlFlow(o, cast<PathDiagnosticControlFlowPiece>(P), FM, SM,
- LangOpts, indent);
- break;
- case PathDiagnosticPiece::Call:
- ReportCall(o, cast<PathDiagnosticCallPiece>(P), FM, SM, LangOpts,
- indent, depth);
- break;
- case PathDiagnosticPiece::Event:
- ReportEvent(o, cast<PathDiagnosticEventPiece>(P), FM, SM, LangOpts,
- indent, depth, isKeyEvent);
- break;
- case PathDiagnosticPiece::Macro:
- ReportMacro(o, cast<PathDiagnosticMacroPiece>(P), FM, SM, LangOpts,
- indent, depth);
- break;
- case PathDiagnosticPiece::Note:
- ReportNote(o, cast<PathDiagnosticNotePiece>(P), FM, SM, LangOpts,
- indent, depth);
- break;
+static void printBugPath(llvm::raw_ostream &o, const FIDMap& FM,
+ AnalyzerOptions &AnOpts,
+ const Preprocessor &PP,
+ const PathPieces &Path) {
+ PlistPrinter Printer(FM, AnOpts, PP);
+ assert(std::is_partitioned(
+ Path.begin(), Path.end(),
+ [](const std::shared_ptr<PathDiagnosticPiece> &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<PathDiagnosticPiece> &E)
+ { return E->getKind() == PathDiagnosticPiece::Note; });
+
+ PathPieces::const_iterator I = Path.begin();
+
+ if (FirstNonNote != Path.begin()) {
+ o << " <key>notes</key>\n"
+ " <array>\n";
+
+ for (; I != FirstNonNote; ++I)
+ Printer.ReportDiag(o, **I);
+
+ o << " </array>\n";
}
+
+ o << " <key>path</key>\n";
+
+ o << " <array>\n";
+
+ for (PathPieces::const_iterator E = Path.end(); I != E; ++I)
+ Printer.ReportDiag(o, **I);
+
+ o << " </array>\n";
+
+ if (!AnOpts.ShouldDisplayMacroExpansions)
+ return;
+
+ o << " <key>macro_expansions</key>\n"
+ " <array>\n";
+ Printer.ReportMacroExpansions(o, /* indent */ 4);
+ o << " </array>\n";
}
+//===----------------------------------------------------------------------===//
+// Methods of PlistDiagnostics.
+//===----------------------------------------------------------------------===//
+
+PlistDiagnostics::PlistDiagnostics(AnalyzerOptions &AnalyzerOpts,
+ const std::string& output,
+ const Preprocessor &PP,
+ bool supportsMultipleFiles)
+ : OutputFile(output), PP(PP), AnOpts(AnalyzerOpts),
+ SupportsCrossFileDiagnostics(supportsMultipleFiles) {}
+
+void ento::createPlistDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
+ PathDiagnosticConsumers &C,
+ const std::string& s,
+ const Preprocessor &PP) {
+ C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
+ /*supportsMultipleFiles*/ false));
+}
+
+void ento::createPlistMultiFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
+ PathDiagnosticConsumers &C,
+ const std::string &s,
+ const Preprocessor &PP) {
+ C.push_back(new PlistDiagnostics(AnalyzerOpts, s, PP,
+ /*supportsMultipleFiles*/ true));
+}
void PlistDiagnostics::FlushDiagnosticsImpl(
std::vector<const PathDiagnostic *> &Diags,
FilesMade *filesMade) {
@@ -335,17 +513,15 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
// ranges of the diagnostics.
FIDMap FM;
SmallVector<FileID, 10> Fids;
- const SourceManager* SM = nullptr;
-
- if (!Diags.empty())
- SM = &Diags.front()->path.front()->getLocation().getManager();
+ const SourceManager& SM = PP.getSourceManager();
+ const LangOptions &LangOpts = PP.getLangOpts();
- auto AddPieceFID = [&FM, &Fids, SM](const PathDiagnosticPiece &Piece) {
- AddFID(FM, Fids, *SM, Piece.getLocation().asLocation());
+ auto AddPieceFID = [&FM, &Fids, &SM](const PathDiagnosticPiece &Piece) {
+ AddFID(FM, Fids, SM, Piece.getLocation().asLocation());
ArrayRef<SourceRange> Ranges = Piece.getRanges();
for (const SourceRange &Range : Ranges) {
- AddFID(FM, Fids, *SM, Range.getBegin());
- AddFID(FM, Fids, *SM, Range.getEnd());
+ AddFID(FM, Fids, SM, Range.getBegin());
+ AddFID(FM, Fids, SM, Range.getEnd());
}
};
@@ -395,14 +571,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
o << "<dict>\n" <<
" <key>clang_version</key>\n";
EmitString(o, getClangFullVersion()) << '\n';
- o << " <key>files</key>\n"
- " <array>\n";
-
- for (FileID FID : Fids)
- EmitString(o << " ", SM->getFileEntryForID(FID)->getName()) << '\n';
-
- o << " </array>\n"
- " <key>diagnostics</key>\n"
+ o << " <key>diagnostics</key>\n"
" <array>\n";
for (std::vector<const PathDiagnostic*>::iterator DI=Diags.begin(),
@@ -411,39 +580,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
o << " <dict>\n";
const PathDiagnostic *D = *DI;
- const PathPieces &PP = D->path;
-
- assert(std::is_partitioned(
- PP.begin(), PP.end(),
- [](const std::shared_ptr<PathDiagnosticPiece> &E)
- { return E->getKind() == PathDiagnosticPiece::Note; }) &&
- "PathDiagnostic is not partitioned so that notes precede the rest");
-
- PathPieces::const_iterator FirstNonNote = std::partition_point(
- PP.begin(), PP.end(),
- [](const std::shared_ptr<PathDiagnosticPiece> &E)
- { return E->getKind() == PathDiagnosticPiece::Note; });
-
- PathPieces::const_iterator I = PP.begin();
-
- if (FirstNonNote != PP.begin()) {
- o << " <key>notes</key>\n"
- " <array>\n";
-
- for (; I != FirstNonNote; ++I)
- ReportDiag(o, **I, FM, *SM, LangOpts);
-
- o << " </array>\n";
- }
-
- o << " <key>path</key>\n";
-
- o << " <array>\n";
-
- for (PathPieces::const_iterator E = PP.end(); I != E; ++I)
- ReportDiag(o, **I, FM, *SM, LangOpts);
-
- o << " </array>\n";
+ printBugPath(o, FM, AnOpts, PP, D->path);
// Output the bug type and bug category.
o << " <key>description</key>";
@@ -458,12 +595,12 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
o << " <!-- This hash is experimental and going to change! -->\n";
o << " <key>issue_hash_content_of_line_in_context</key>";
PathDiagnosticLocation UPDLoc = D->getUniqueingLoc();
- FullSourceLoc L(SM->getExpansionLoc(UPDLoc.isValid()
+ FullSourceLoc L(SM.getExpansionLoc(UPDLoc.isValid()
? UPDLoc.asLocation()
: D->getLocation().asLocation()),
- *SM);
+ SM);
const Decl *DeclWithIssue = D->getDeclWithIssue();
- EmitString(o, GetIssueHash(*SM, L, D->getCheckName(), D->getBugType(),
+ EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(),
DeclWithIssue, LangOpts))
<< '\n';
@@ -507,15 +644,17 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
// the leak location even after code is added between the allocation
// site and the end of scope (leak report location).
if (UPDLoc.isValid()) {
- FullSourceLoc UFunL(SM->getExpansionLoc(
- D->getUniqueingDecl()->getBody()->getLocStart()), *SM);
+ FullSourceLoc UFunL(
+ SM.getExpansionLoc(
+ D->getUniqueingDecl()->getBody()->getBeginLoc()),
+ SM);
o << " <key>issue_hash_function_offset</key><string>"
<< L.getExpansionLineNumber() - UFunL.getExpansionLineNumber()
<< "</string>\n";
// Otherwise, use the location on which the bug is reported.
} else {
- FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM);
+ FullSourceLoc FunL(SM.getExpansionLoc(Body->getBeginLoc()), SM);
o << " <key>issue_hash_function_offset</key><string>"
<< L.getExpansionLineNumber() - FunL.getExpansionLineNumber()
<< "</string>\n";
@@ -527,7 +666,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
// Output the location of the bug.
o << " <key>location</key>\n";
- EmitLocation(o, *SM, D->getLocation().asLocation(), FM, 2);
+ EmitLocation(o, SM, D->getLocation().asLocation(), FM, 2);
// Output the diagnostic to the sub-diagnostic client, if any.
if (!filesMade->empty()) {
@@ -551,13 +690,21 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
}
}
+ printCoverage(D, /*IndentLevel=*/2, Fids, FM, o);
+
// Close up the entry.
o << " </dict>\n";
}
o << " </array>\n";
- if (llvm::AreStatisticsEnabled() && SerializeStatistics) {
+ o << " <key>files</key>\n"
+ " <array>\n";
+ for (FileID FID : Fids)
+ EmitString(o << " ", SM.getFileEntryForID(FID)->getName()) << '\n';
+ o << " </array>\n";
+
+ if (llvm::AreStatisticsEnabled() && AnOpts.ShouldSerializeStats) {
o << " <key>statistics</key>\n";
std::string stats;
llvm::raw_string_ostream os(stats);
@@ -569,3 +716,402 @@ void PlistDiagnostics::FlushDiagnosticsImpl(
// Finish.
o << "</dict>\n</plist>";
}
+
+//===----------------------------------------------------------------------===//
+// Declarations of helper functions and data structures for expanding macros.
+//===----------------------------------------------------------------------===//
+
+namespace {
+
+using ExpArgTokens = llvm::SmallVector<Token, 2>;
+
+/// Maps unexpanded macro arguments to expanded arguments. A macro argument may
+/// need to expanded further when it is nested inside another macro.
+class MacroArgMap : public std::map<const IdentifierInfo *, ExpArgTokens> {
+public:
+ void expandFromPrevMacro(const MacroArgMap &Super);
+};
+
+struct MacroNameAndArgs {
+ std::string Name;
+ const MacroInfo *MI = nullptr;
+ MacroArgMap Args;
+
+ MacroNameAndArgs(std::string N, const MacroInfo *MI, MacroArgMap M)
+ : Name(std::move(N)), MI(MI), Args(std::move(M)) {}
+};
+
+class TokenPrinter {
+ llvm::raw_ostream &OS;
+ const Preprocessor &PP;
+
+ Token PrevTok, PrevPrevTok;
+ TokenConcatenation ConcatInfo;
+
+public:
+ TokenPrinter(llvm::raw_ostream &OS, const Preprocessor &PP)
+ : OS(OS), PP(PP), ConcatInfo(PP) {
+ PrevTok.setKind(tok::unknown);
+ PrevPrevTok.setKind(tok::unknown);
+ }
+
+ void printToken(const Token &Tok);
+};
+
+} // end of anonymous namespace
+
+/// The implementation method of getMacroExpansion: It prints the expansion of
+/// a macro to \p Printer, and returns with the name of the macro.
+///
+/// Since macros can be nested in one another, this function may call itself
+/// recursively.
+///
+/// Unfortunately, macro arguments have to expanded manually. To understand why,
+/// observe the following example:
+///
+/// #define PRINT(x) print(x)
+/// #define DO_SOMETHING(str) PRINT(str)
+///
+/// DO_SOMETHING("Cute panda cubs.");
+///
+/// As we expand the last line, we'll immediately replace PRINT(str) with
+/// print(x). The information that both 'str' and 'x' refers to the same string
+/// is an information we have to forward, hence the argument \p PrevArgs.
+static std::string getMacroNameAndPrintExpansion(TokenPrinter &Printer,
+ SourceLocation MacroLoc,
+ const Preprocessor &PP,
+ const MacroArgMap &PrevArgs);
+
+/// Retrieves the name of the macro and what it's arguments expand into
+/// at \p ExpanLoc.
+///
+/// For example, for the following macro expansion:
+///
+/// #define SET_TO_NULL(x) x = 0
+/// #define NOT_SUSPICIOUS(a) \
+/// { \
+/// int b = 0; \
+/// } \
+/// SET_TO_NULL(a)
+///
+/// int *ptr = new int(4);
+/// NOT_SUSPICIOUS(&ptr);
+/// *ptr = 5;
+///
+/// When \p ExpanLoc references the last line, the macro name "NOT_SUSPICIOUS"
+/// and the MacroArgMap map { (a, &ptr) } will be returned.
+///
+/// When \p ExpanLoc references "SET_TO_NULL(a)" within the definition of
+/// "NOT_SUSPICOUS", the macro name "SET_TO_NULL" and the MacroArgMap map
+/// { (x, a) } will be returned.
+static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc,
+ const Preprocessor &PP);
+
+/// Retrieves the ')' token that matches '(' \p It points to.
+static MacroInfo::tokens_iterator getMatchingRParen(
+ MacroInfo::tokens_iterator It,
+ MacroInfo::tokens_iterator End);
+
+/// Retrieves the macro info for \p II refers to at \p Loc. This is important
+/// because macros can be redefined or undefined.
+static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP,
+ const SourceManager &SM,
+ const IdentifierInfo *II,
+ SourceLocation Loc);
+
+//===----------------------------------------------------------------------===//
+// Definitions of helper functions and methods for expanding macros.
+//===----------------------------------------------------------------------===//
+
+static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc,
+ const Preprocessor &PP) {
+
+ llvm::SmallString<200> ExpansionBuf;
+ llvm::raw_svector_ostream OS(ExpansionBuf);
+ TokenPrinter Printer(OS, PP);
+ std::string MacroName =
+ getMacroNameAndPrintExpansion(Printer, MacroLoc, PP, MacroArgMap{});
+ return { MacroName, OS.str() };
+}
+
+static std::string getMacroNameAndPrintExpansion(TokenPrinter &Printer,
+ SourceLocation MacroLoc,
+ const Preprocessor &PP,
+ const MacroArgMap &PrevArgs) {
+
+ const SourceManager &SM = PP.getSourceManager();
+
+ MacroNameAndArgs Info = getMacroNameAndArgs(SM.getExpansionLoc(MacroLoc), PP);
+
+ // Manually expand its arguments from the previous macro.
+ Info.Args.expandFromPrevMacro(PrevArgs);
+
+ // Iterate over the macro's tokens and stringify them.
+ for (auto It = Info.MI->tokens_begin(), E = Info.MI->tokens_end(); It != E;
+ ++It) {
+ Token T = *It;
+
+ // If this token is not an identifier, we only need to print it.
+ if (T.isNot(tok::identifier)) {
+ Printer.printToken(T);
+ continue;
+ }
+
+ const auto *II = T.getIdentifierInfo();
+ assert(II &&
+ "This token is an identifier but has no IdentifierInfo!");
+
+ // If this token is a macro that should be expanded inside the current
+ // macro.
+ if (const MacroInfo *MI =
+ getMacroInfoForLocation(PP, SM, II, T.getLocation())) {
+ getMacroNameAndPrintExpansion(Printer, T.getLocation(), PP, Info.Args);
+
+ // If this is a function-like macro, skip its arguments, as
+ // getExpandedMacro() already printed them. If this is the case, let's
+ // first jump to the '(' token.
+ if (MI->getNumParams() != 0)
+ It = getMatchingRParen(++It, E);
+ continue;
+ }
+
+ // If this token is the current macro's argument, we should expand it.
+ auto ArgMapIt = Info.Args.find(II);
+ if (ArgMapIt != Info.Args.end()) {
+ for (MacroInfo::tokens_iterator ArgIt = ArgMapIt->second.begin(),
+ ArgEnd = ArgMapIt->second.end();
+ ArgIt != ArgEnd; ++ArgIt) {
+
+ // These tokens may still be macros, if that is the case, handle it the
+ // same way we did above.
+ const auto *ArgII = ArgIt->getIdentifierInfo();
+ if (!ArgII) {
+ Printer.printToken(*ArgIt);
+ continue;
+ }
+
+ const auto *MI = PP.getMacroInfo(ArgII);
+ if (!MI) {
+ Printer.printToken(*ArgIt);
+ continue;
+ }
+
+ getMacroNameAndPrintExpansion(Printer, ArgIt->getLocation(), PP,
+ Info.Args);
+ if (MI->getNumParams() != 0)
+ ArgIt = getMatchingRParen(++ArgIt, ArgEnd);
+ }
+ continue;
+ }
+
+ // If control reached here, then this token isn't a macro identifier, nor an
+ // unexpanded macro argument that we need to handle, print it.
+ Printer.printToken(T);
+ }
+
+ return Info.Name;
+}
+
+static MacroNameAndArgs getMacroNameAndArgs(SourceLocation ExpanLoc,
+ const Preprocessor &PP) {
+
+ const SourceManager &SM = PP.getSourceManager();
+ const LangOptions &LangOpts = PP.getLangOpts();
+
+ // First, we create a Lexer to lex *at the expansion location* the tokens
+ // referring to the macro's name and its arguments.
+ std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(ExpanLoc);
+ const llvm::MemoryBuffer *MB = SM.getBuffer(LocInfo.first);
+ const char *MacroNameTokenPos = MB->getBufferStart() + LocInfo.second;
+
+ Lexer RawLexer(SM.getLocForStartOfFile(LocInfo.first), LangOpts,
+ MB->getBufferStart(), MacroNameTokenPos, MB->getBufferEnd());
+
+ // Acquire the macro's name.
+ Token TheTok;
+ RawLexer.LexFromRawLexer(TheTok);
+
+ std::string MacroName = PP.getSpelling(TheTok);
+
+ const auto *II = PP.getIdentifierInfo(MacroName);
+ assert(II && "Failed to acquire the IndetifierInfo for the macro!");
+
+ const MacroInfo *MI = getMacroInfoForLocation(PP, SM, II, ExpanLoc);
+ assert(MI && "The macro must've been defined at it's expansion location!");
+
+ // Acquire the macro's arguments.
+ //
+ // The rough idea here is to lex from the first left parentheses to the last
+ // right parentheses, and map the macro's unexpanded arguments to what they
+ // will be expanded to. An expanded macro argument may contain several tokens
+ // (like '3 + 4'), so we'll lex until we find a tok::comma or tok::r_paren, at
+ // which point we start lexing the next argument or finish.
+ ArrayRef<const IdentifierInfo *> MacroArgs = MI->params();
+ if (MacroArgs.empty())
+ return { MacroName, MI, {} };
+
+ RawLexer.LexFromRawLexer(TheTok);
+ assert(TheTok.is(tok::l_paren) &&
+ "The token after the macro's identifier token should be '('!");
+
+ MacroArgMap Args;
+
+ // When the macro's argument is a function call, like
+ // CALL_FN(someFunctionName(param1, param2))
+ // we will find tok::l_paren, tok::r_paren, and tok::comma that do not divide
+ // actual macro arguments, or do not represent the macro argument's closing
+ // parentheses, so we'll count how many parentheses aren't closed yet.
+ // If ParanthesesDepth
+ // * = 0, then there are no more arguments to lex.
+ // * = 1, then if we find a tok::comma, we can start lexing the next arg.
+ // * > 1, then tok::comma is a part of the current arg.
+ int ParenthesesDepth = 1;
+
+ // If we encounter __VA_ARGS__, we will lex until the closing tok::r_paren,
+ // even if we lex a tok::comma and ParanthesesDepth == 1.
+ const IdentifierInfo *__VA_ARGS__II = PP.getIdentifierInfo("__VA_ARGS__");
+
+ for (const IdentifierInfo *UnexpArgII : MacroArgs) {
+ MacroArgMap::mapped_type ExpandedArgTokens;
+
+ // One could also simply not supply a single argument to __VA_ARGS__ -- this
+ // results in a preprocessor warning, but is not an error:
+ // #define VARIADIC(ptr, ...) \
+ // someVariadicTemplateFunction(__VA_ARGS__)
+ //
+ // int *ptr;
+ // VARIADIC(ptr); // Note that there are no commas, this isn't just an
+ // // empty parameter -- there are no parameters for '...'.
+ // In any other case, ParenthesesDepth mustn't be 0 here.
+ if (ParenthesesDepth != 0) {
+
+ // Lex the first token of the next macro parameter.
+ RawLexer.LexFromRawLexer(TheTok);
+
+ while (!(ParenthesesDepth == 1 &&
+ (UnexpArgII == __VA_ARGS__II ? false : TheTok.is(tok::comma)))) {
+ assert(TheTok.isNot(tok::eof) &&
+ "EOF encountered while looking for expanded macro args!");
+
+ if (TheTok.is(tok::l_paren))
+ ++ParenthesesDepth;
+
+ if (TheTok.is(tok::r_paren))
+ --ParenthesesDepth;
+
+ if (ParenthesesDepth == 0)
+ break;
+
+ if (TheTok.is(tok::raw_identifier))
+ PP.LookUpIdentifierInfo(TheTok);
+
+ ExpandedArgTokens.push_back(TheTok);
+ RawLexer.LexFromRawLexer(TheTok);
+ }
+ } else {
+ assert(UnexpArgII == __VA_ARGS__II);
+ }
+
+ Args.emplace(UnexpArgII, std::move(ExpandedArgTokens));
+ }
+
+ assert(TheTok.is(tok::r_paren) &&
+ "Expanded macro argument acquisition failed! After the end of the loop"
+ " this token should be ')'!");
+
+ return { MacroName, MI, Args };
+}
+
+static MacroInfo::tokens_iterator getMatchingRParen(
+ MacroInfo::tokens_iterator It,
+ MacroInfo::tokens_iterator End) {
+
+ assert(It->is(tok::l_paren) && "This token should be '('!");
+
+ // Skip until we find the closing ')'.
+ int ParenthesesDepth = 1;
+ while (ParenthesesDepth != 0) {
+ ++It;
+
+ assert(It->isNot(tok::eof) &&
+ "Encountered EOF while attempting to skip macro arguments!");
+ assert(It != End &&
+ "End of the macro definition reached before finding ')'!");
+
+ if (It->is(tok::l_paren))
+ ++ParenthesesDepth;
+
+ if (It->is(tok::r_paren))
+ --ParenthesesDepth;
+ }
+ return It;
+}
+
+static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP,
+ const SourceManager &SM,
+ const IdentifierInfo *II,
+ SourceLocation Loc) {
+
+ const MacroDirective *MD = PP.getLocalMacroDirectiveHistory(II);
+ if (!MD)
+ return nullptr;
+
+ return MD->findDirectiveAtLoc(Loc, SM).getMacroInfo();
+}
+
+void MacroArgMap::expandFromPrevMacro(const MacroArgMap &Super) {
+
+ for (value_type &Pair : *this) {
+ ExpArgTokens &CurrExpArgTokens = Pair.second;
+
+ // For each token in the expanded macro argument.
+ auto It = CurrExpArgTokens.begin();
+ while (It != CurrExpArgTokens.end()) {
+ if (It->isNot(tok::identifier)) {
+ ++It;
+ continue;
+ }
+
+ const auto *II = It->getIdentifierInfo();
+ assert(II);
+
+ // Is this an argument that "Super" expands further?
+ if (!Super.count(II)) {
+ ++It;
+ continue;
+ }
+
+ const ExpArgTokens &SuperExpArgTokens = Super.at(II);
+
+ It = CurrExpArgTokens.insert(
+ It, SuperExpArgTokens.begin(), SuperExpArgTokens.end());
+ std::advance(It, SuperExpArgTokens.size());
+ It = CurrExpArgTokens.erase(It);
+ }
+ }
+}
+
+void TokenPrinter::printToken(const Token &Tok) {
+ // If this is the first token to be printed, don't print space.
+ if (PrevTok.isNot(tok::unknown)) {
+ // If the tokens were already space separated, or if they must be to avoid
+ // them being implicitly pasted, add a space between them.
+ if(Tok.hasLeadingSpace() || ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok,
+ Tok)) {
+ // AvoidConcat doesn't check for ##, don't print a space around it.
+ if (PrevTok.isNot(tok::hashhash) && Tok.isNot(tok::hashhash)) {
+ OS << ' ';
+ }
+ }
+ }
+
+ if (!Tok.isOneOf(tok::hash, tok::hashhash)) {
+ if (PrevTok.is(tok::hash))
+ OS << '\"' << PP.getSpelling(Tok) << '\"';
+ else
+ OS << PP.getSpelling(Tok);
+ }
+
+ PrevPrevTok = PrevTok;
+ PrevTok = Tok;
+}
diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp
index 94e2e00d8bbc..2e2e2ec94f39 100644
--- a/lib/StaticAnalyzer/Core/ProgramState.cpp
+++ b/lib/StaticAnalyzer/Core/ProgramState.cpp
@@ -69,6 +69,10 @@ ProgramState::~ProgramState() {
stateMgr->getStoreManager().decrementReferenceCount(store);
}
+int64_t ProgramState::getID() const {
+ return getStateManager().Alloc.identifyKnownAlignedObject<ProgramState>(this);
+}
+
ProgramStateManager::ProgramStateManager(ASTContext &Ctx,
StoreManagerCreator CreateSMgr,
ConstraintManagerCreator CreateCMgr,
@@ -121,8 +125,8 @@ ProgramStateRef ProgramState::bindLoc(Loc LV,
ProgramStateRef newState = makeWithStore(Mgr.StoreMgr->Bind(getStore(),
LV, V));
const MemRegion *MR = LV.getAsRegion();
- if (MR && Mgr.getOwningEngine() && notifyChanges)
- return Mgr.getOwningEngine()->processRegionChange(newState, MR, LCtx);
+ if (MR && notifyChanges)
+ return Mgr.getOwningEngine().processRegionChange(newState, MR, LCtx);
return newState;
}
@@ -134,9 +138,7 @@ ProgramState::bindDefaultInitial(SVal loc, SVal V,
const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion();
const StoreRef &newStore = Mgr.StoreMgr->BindDefaultInitial(getStore(), R, V);
ProgramStateRef new_state = makeWithStore(newStore);
- return Mgr.getOwningEngine()
- ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx)
- : new_state;
+ return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx);
}
ProgramStateRef
@@ -145,9 +147,7 @@ ProgramState::bindDefaultZero(SVal loc, const LocationContext *LCtx) const {
const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion();
const StoreRef &newStore = Mgr.StoreMgr->BindDefaultZero(getStore(), R);
ProgramStateRef new_state = makeWithStore(newStore);
- return Mgr.getOwningEngine()
- ? Mgr.getOwningEngine()->processRegionChange(new_state, R, LCtx)
- : new_state;
+ return Mgr.getOwningEngine().processRegionChange(new_state, R, LCtx);
}
typedef ArrayRef<const MemRegion *> RegionList;
@@ -192,41 +192,34 @@ ProgramState::invalidateRegionsImpl(ValueList Values,
RegionAndSymbolInvalidationTraits *ITraits,
const CallEvent *Call) const {
ProgramStateManager &Mgr = getStateManager();
- SubEngine* Eng = Mgr.getOwningEngine();
+ SubEngine &Eng = Mgr.getOwningEngine();
- InvalidatedSymbols Invalidated;
+ InvalidatedSymbols InvalidatedSyms;
if (!IS)
- IS = &Invalidated;
+ IS = &InvalidatedSyms;
RegionAndSymbolInvalidationTraits ITraitsLocal;
if (!ITraits)
ITraits = &ITraitsLocal;
- if (Eng) {
- StoreManager::InvalidatedRegions TopLevelInvalidated;
- StoreManager::InvalidatedRegions Invalidated;
- const StoreRef &newStore
- = Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call,
- *IS, *ITraits, &TopLevelInvalidated,
- &Invalidated);
-
- ProgramStateRef newState = makeWithStore(newStore);
-
- if (CausedByPointerEscape) {
- newState = Eng->notifyCheckersOfPointerEscape(newState, IS,
- TopLevelInvalidated,
- Invalidated, Call,
- *ITraits);
- }
+ StoreManager::InvalidatedRegions TopLevelInvalidated;
+ StoreManager::InvalidatedRegions Invalidated;
+ const StoreRef &newStore
+ = Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call,
+ *IS, *ITraits, &TopLevelInvalidated,
+ &Invalidated);
+
+ ProgramStateRef newState = makeWithStore(newStore);
- return Eng->processRegionChanges(newState, IS, TopLevelInvalidated,
- Invalidated, LCtx, Call);
+ if (CausedByPointerEscape) {
+ newState = Eng.notifyCheckersOfPointerEscape(newState, IS,
+ TopLevelInvalidated,
+ Call,
+ *ITraits);
}
- const StoreRef &newStore =
- Mgr.StoreMgr->invalidateRegions(getStore(), Values, E, Count, LCtx, Call,
- *IS, *ITraits, nullptr, nullptr);
- return makeWithStore(newStore);
+ return Eng.processRegionChanges(newState, IS, TopLevelInvalidated,
+ Invalidated, LCtx, Call);
}
ProgramStateRef ProgramState::killBinding(Loc LV) const {
@@ -449,14 +442,16 @@ void ProgramState::setStore(const StoreRef &newStore) {
// State pretty-printing.
//===----------------------------------------------------------------------===//
-void ProgramState::print(raw_ostream &Out, const char *NL, const char *Sep,
+void ProgramState::print(raw_ostream &Out,
+ const char *NL, const char *Sep,
const LocationContext *LC) const {
// Print the store.
ProgramStateManager &Mgr = getStateManager();
- Mgr.getStoreManager().print(getStore(), Out, NL, Sep);
+ const ASTContext &Context = getStateManager().getContext();
+ Mgr.getStoreManager().print(getStore(), Out, NL);
// Print out the environment.
- Env.print(Out, NL, Sep, LC);
+ Env.print(Out, NL, Sep, Context, LC);
// Print out the constraints.
Mgr.getConstraintManager().print(this, Out, NL, Sep);
@@ -465,13 +460,14 @@ void ProgramState::print(raw_ostream &Out, const char *NL, const char *Sep,
printDynamicTypeInfo(this, Out, NL, Sep);
// Print out tainted symbols.
- printTaint(Out, NL, Sep);
+ printTaint(Out, NL);
// Print checker-specific data.
- Mgr.getOwningEngine()->printState(Out, this, NL, Sep, LC);
+ Mgr.getOwningEngine().printState(Out, this, NL, Sep, LC);
}
-void ProgramState::printDOT(raw_ostream &Out, const LocationContext *LC) const {
+void ProgramState::printDOT(raw_ostream &Out,
+ const LocationContext *LC) const {
print(Out, "\\l", "\\|", LC);
}
@@ -480,7 +476,7 @@ LLVM_DUMP_METHOD void ProgramState::dump() const {
}
void ProgramState::printTaint(raw_ostream &Out,
- const char *NL, const char *Sep) const {
+ const char *NL) const {
TaintMapImpl TM = get<TaintMap>();
if (!TM.isEmpty())
@@ -496,7 +492,7 @@ void ProgramState::dumpTaint() const {
}
AnalysisManager& ProgramState::getAnalysisManager() const {
- return stateMgr->getOwningEngine()->getAnalysisManager();
+ return stateMgr->getOwningEngine().getAnalysisManager();
}
//===----------------------------------------------------------------------===//
@@ -652,22 +648,12 @@ bool ProgramState::scanReachableSymbols(SVal val, SymbolVisitor& visitor) const
return S.scan(val);
}
-bool ProgramState::scanReachableSymbols(const SVal *I, const SVal *E,
- SymbolVisitor &visitor) const {
+bool ProgramState::scanReachableSymbols(
+ llvm::iterator_range<region_iterator> Reachable,
+ SymbolVisitor &visitor) const {
ScanReachableSymbols S(this, visitor);
- for ( ; I != E; ++I) {
- if (!S.scan(*I))
- return false;
- }
- return true;
-}
-
-bool ProgramState::scanReachableSymbols(const MemRegion * const *I,
- const MemRegion * const *E,
- SymbolVisitor &visitor) const {
- ScanReachableSymbols S(this, visitor);
- for ( ; I != E; ++I) {
- if (!S.scan(*I))
+ for (const MemRegion *R : Reachable) {
+ if (!S.scan(R))
return false;
}
return true;
@@ -835,4 +821,3 @@ bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const {
return false;
}
-
diff --git a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp
index e8c7bdbde385..d9b58d0f5185 100644
--- a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp
+++ b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp
@@ -399,7 +399,7 @@ RangeConstraintManager::removeDeadBindings(ProgramStateRef State,
for (ConstraintRangeTy::iterator I = CR.begin(), E = CR.end(); I != E; ++I) {
SymbolRef Sym = I.getKey();
- if (SymReaper.maybeDead(Sym)) {
+ if (SymReaper.isDead(Sym)) {
Changed = true;
CR = CRFactory.remove(CR, Sym);
}
diff --git a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp
index f99853f07073..146dc20ad021 100644
--- a/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp
+++ b/lib/StaticAnalyzer/Core/RangedConstraintManager.cpp
@@ -200,6 +200,11 @@ void RangedConstraintManager::computeAdjustment(SymbolRef &Sym,
}
}
+void *ProgramStateTrait<ConstraintRange>::GDMIndex() {
+ static int Index;
+ return &Index;
+}
+
} // end of namespace ento
} // end of namespace clang
diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp
index db6449e6d5f3..b2339be4f263 100644
--- a/lib/StaticAnalyzer/Core/RegionStore.cpp
+++ b/lib/StaticAnalyzer/Core/RegionStore.cpp
@@ -17,6 +17,7 @@
#include "clang/AST/Attr.h"
#include "clang/AST/CharUnits.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Analysis/Analyses/LiveVariables.h"
#include "clang/Analysis/AnalysisDeclContext.h"
#include "clang/Basic/TargetInfo.h"
@@ -61,7 +62,9 @@ private:
: P(r, k), Data(offset) {
assert(r && "Must have known regions.");
assert(getOffset() == offset && "Failed to store offset");
- assert((r == r->getBaseRegion() || isa<ObjCIvarRegion>(r)) && "Not a base");
+ assert((r == r->getBaseRegion() || isa<ObjCIvarRegion>(r) ||
+ isa <CXXDerivedObjectRegion>(r)) &&
+ "Not a base");
}
public:
@@ -308,7 +311,7 @@ public:
//===----------------------------------------------------------------------===//
namespace {
-class invalidateRegionsWorker;
+class InvalidateRegionsWorker;
class RegionStoreManager : public StoreManager {
public:
@@ -335,7 +338,7 @@ private:
/// A helper used to populate the work list with the given set of
/// regions.
- void populateWorkList(invalidateRegionsWorker &W,
+ void populateWorkList(InvalidateRegionsWorker &W,
ArrayRef<SVal> Values,
InvalidatedRegions *TopLevelRegions);
@@ -344,11 +347,9 @@ public:
: StoreManager(mgr), Features(f),
RBFactory(mgr.getAllocator()), CBFactory(mgr.getAllocator()),
SmallStructLimit(0) {
- if (SubEngine *Eng = StateMgr.getOwningEngine()) {
- AnalyzerOptions &Options = Eng->getAnalysisManager().options;
- SmallStructLimit =
- Options.getOptionAsInteger("region-store-small-struct-limit", 2);
- }
+ SubEngine &Eng = StateMgr.getOwningEngine();
+ AnalyzerOptions &Options = Eng.getAnalysisManager().options;
+ SmallStructLimit = Options.RegionStoreSmallStructLimit;
}
@@ -598,8 +599,7 @@ public: // Part of public interface to class.
RBFactory.getTreeFactory());
}
- void print(Store store, raw_ostream &Out, const char* nl,
- const char *sep) override;
+ void print(Store store, raw_ostream &Out, const char* nl) override;
void iterBindings(Store store, BindingsHandler& f) override {
RegionBindingsRef B = getRegionBindings(store);
@@ -945,7 +945,7 @@ RegionStoreManager::removeSubRegionBindings(RegionBindingsConstRef B,
}
namespace {
-class invalidateRegionsWorker : public ClusterAnalysis<invalidateRegionsWorker>
+class InvalidateRegionsWorker : public ClusterAnalysis<InvalidateRegionsWorker>
{
const Expr *Ex;
unsigned Count;
@@ -955,7 +955,7 @@ class invalidateRegionsWorker : public ClusterAnalysis<invalidateRegionsWorker>
StoreManager::InvalidatedRegions *Regions;
GlobalsFilterKind GlobalsFilter;
public:
- invalidateRegionsWorker(RegionStoreManager &rm,
+ InvalidateRegionsWorker(RegionStoreManager &rm,
ProgramStateManager &stateMgr,
RegionBindingsRef b,
const Expr *ex, unsigned count,
@@ -964,7 +964,7 @@ public:
RegionAndSymbolInvalidationTraits &ITraitsIn,
StoreManager::InvalidatedRegions *r,
GlobalsFilterKind GFK)
- : ClusterAnalysis<invalidateRegionsWorker>(rm, stateMgr, b),
+ : ClusterAnalysis<InvalidateRegionsWorker>(rm, stateMgr, b),
Ex(ex), Count(count), LCtx(lctx), IS(is), ITraits(ITraitsIn), Regions(r),
GlobalsFilter(GFK) {}
@@ -985,14 +985,14 @@ public:
};
}
-bool invalidateRegionsWorker::AddToWorkList(const MemRegion *R) {
+bool InvalidateRegionsWorker::AddToWorkList(const MemRegion *R) {
bool doNotInvalidateSuperRegion = ITraits.hasTrait(
R, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion);
const MemRegion *BaseR = doNotInvalidateSuperRegion ? R : R->getBaseRegion();
return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR));
}
-void invalidateRegionsWorker::VisitBinding(SVal V) {
+void InvalidateRegionsWorker::VisitBinding(SVal V) {
// A symbol? Mark it touched by the invalidation.
if (SymbolRef Sym = V.getAsSymbol())
IS.insert(Sym);
@@ -1017,7 +1017,7 @@ void invalidateRegionsWorker::VisitBinding(SVal V) {
}
}
-void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
+void InvalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
const ClusterBindings *C) {
bool PreserveRegionsContents =
@@ -1033,6 +1033,32 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
B = B.remove(baseR);
}
+ if (const auto *TO = dyn_cast<TypedValueRegion>(baseR)) {
+ if (const auto *RD = TO->getValueType()->getAsCXXRecordDecl()) {
+
+ // Lambdas can affect all static local variables without explicitly
+ // capturing those.
+ // We invalidate all static locals referenced inside the lambda body.
+ if (RD->isLambda() && RD->getLambdaCallOperator()->getBody()) {
+ using namespace ast_matchers;
+
+ const char *DeclBind = "DeclBind";
+ StatementMatcher RefToStatic = stmt(hasDescendant(declRefExpr(
+ to(varDecl(hasStaticStorageDuration()).bind(DeclBind)))));
+ auto Matches =
+ match(RefToStatic, *RD->getLambdaCallOperator()->getBody(),
+ RD->getASTContext());
+
+ for (BoundNodes &Match : Matches) {
+ auto *VD = Match.getNodeAs<VarDecl>(DeclBind);
+ const VarRegion *ToInvalidate =
+ RM.getRegionManager().getVarRegion(VD, LCtx);
+ AddToWorkList(ToInvalidate);
+ }
+ }
+ }
+ }
+
// BlockDataRegion? If so, invalidate captured variables that are passed
// by reference.
if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(baseR)) {
@@ -1181,7 +1207,7 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR,
B = B.addBinding(baseR, BindingKey::Direct, V);
}
-bool invalidateRegionsWorker::isInitiallyIncludedGlobalRegion(
+bool InvalidateRegionsWorker::isInitiallyIncludedGlobalRegion(
const MemRegion *R) {
switch (GlobalsFilter) {
case GFK_None:
@@ -1195,7 +1221,7 @@ bool invalidateRegionsWorker::isInitiallyIncludedGlobalRegion(
llvm_unreachable("unknown globals filter");
}
-bool invalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) {
+bool InvalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) {
if (isInitiallyIncludedGlobalRegion(Base))
return true;
@@ -1229,7 +1255,7 @@ RegionStoreManager::invalidateGlobalRegion(MemRegion::Kind K,
return B;
}
-void RegionStoreManager::populateWorkList(invalidateRegionsWorker &W,
+void RegionStoreManager::populateWorkList(InvalidateRegionsWorker &W,
ArrayRef<SVal> Values,
InvalidatedRegions *TopLevelRegions) {
for (ArrayRef<SVal>::iterator I = Values.begin(),
@@ -1280,7 +1306,7 @@ RegionStoreManager::invalidateRegions(Store store,
}
RegionBindingsRef B = getRegionBindings(store);
- invalidateRegionsWorker W(*this, StateMgr, B, Ex, Count, LCtx, IS, ITraits,
+ InvalidateRegionsWorker W(*this, StateMgr, B, Ex, Count, LCtx, IS, ITraits,
Invalidated, GlobalsFilter);
// Scan the bindings and generate the clusters.
@@ -1302,11 +1328,11 @@ RegionStoreManager::invalidateRegions(Store store,
case GFK_All:
B = invalidateGlobalRegion(MemRegion::GlobalInternalSpaceRegionKind,
Ex, Count, LCtx, B, Invalidated);
- // FALLTHROUGH
+ LLVM_FALLTHROUGH;
case GFK_SystemOnly:
B = invalidateGlobalRegion(MemRegion::GlobalSystemSpaceRegionKind,
Ex, Count, LCtx, B, Invalidated);
- // FALLTHROUGH
+ LLVM_FALLTHROUGH;
case GFK_None:
break;
}
@@ -2363,40 +2389,45 @@ RegionStoreManager::bindAggregate(RegionBindingsConstRef B,
//===----------------------------------------------------------------------===//
namespace {
-class removeDeadBindingsWorker :
- public ClusterAnalysis<removeDeadBindingsWorker> {
- SmallVector<const SymbolicRegion*, 12> Postponed;
+class RemoveDeadBindingsWorker
+ : public ClusterAnalysis<RemoveDeadBindingsWorker> {
+ using ChildrenListTy = SmallVector<const SymbolDerived *, 4>;
+ using MapParentsToDerivedTy = llvm::DenseMap<SymbolRef, ChildrenListTy>;
+
+ MapParentsToDerivedTy ParentsToDerived;
SymbolReaper &SymReaper;
const StackFrameContext *CurrentLCtx;
public:
- removeDeadBindingsWorker(RegionStoreManager &rm,
+ RemoveDeadBindingsWorker(RegionStoreManager &rm,
ProgramStateManager &stateMgr,
RegionBindingsRef b, SymbolReaper &symReaper,
const StackFrameContext *LCtx)
- : ClusterAnalysis<removeDeadBindingsWorker>(rm, stateMgr, b),
+ : ClusterAnalysis<RemoveDeadBindingsWorker>(rm, stateMgr, b),
SymReaper(symReaper), CurrentLCtx(LCtx) {}
// Called by ClusterAnalysis.
void VisitAddedToCluster(const MemRegion *baseR, const ClusterBindings &C);
void VisitCluster(const MemRegion *baseR, const ClusterBindings *C);
- using ClusterAnalysis<removeDeadBindingsWorker>::VisitCluster;
+ using ClusterAnalysis<RemoveDeadBindingsWorker>::VisitCluster;
using ClusterAnalysis::AddToWorkList;
bool AddToWorkList(const MemRegion *R);
- bool UpdatePostponed();
void VisitBinding(SVal V);
+
+private:
+ void populateWorklistFromSymbol(SymbolRef s);
};
}
-bool removeDeadBindingsWorker::AddToWorkList(const MemRegion *R) {
+bool RemoveDeadBindingsWorker::AddToWorkList(const MemRegion *R) {
const MemRegion *BaseR = R->getBaseRegion();
return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR));
}
-void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR,
+void RemoveDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR,
const ClusterBindings &C) {
if (const VarRegion *VR = dyn_cast<VarRegion>(baseR)) {
@@ -2407,10 +2438,11 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR,
}
if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(baseR)) {
- if (SymReaper.isLive(SR->getSymbol()))
+ if (SymReaper.isLive(SR->getSymbol())) {
AddToWorkList(SR, &C);
- else
- Postponed.push_back(SR);
+ } else if (const auto *SD = dyn_cast<SymbolDerived>(SR->getSymbol())) {
+ ParentsToDerived[SD->getParentSymbol()].push_back(SD);
+ }
return;
}
@@ -2422,7 +2454,7 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR,
// CXXThisRegion in the current or parent location context is live.
if (const CXXThisRegion *TR = dyn_cast<CXXThisRegion>(baseR)) {
- const StackArgumentsSpaceRegion *StackReg =
+ const auto *StackReg =
cast<StackArgumentsSpaceRegion>(TR->getSuperRegion());
const StackFrameContext *RegCtx = StackReg->getStackFrame();
if (CurrentLCtx &&
@@ -2431,7 +2463,7 @@ void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR,
}
}
-void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR,
+void RemoveDeadBindingsWorker::VisitCluster(const MemRegion *baseR,
const ClusterBindings *C) {
if (!C)
return;
@@ -2449,7 +2481,7 @@ void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR,
}
}
-void removeDeadBindingsWorker::VisitBinding(SVal V) {
+void RemoveDeadBindingsWorker::VisitBinding(SVal V) {
// Is it a LazyCompoundVal? All referenced regions are live as well.
if (Optional<nonloc::LazyCompoundVal> LCS =
V.getAs<nonloc::LazyCompoundVal>()) {
@@ -2467,6 +2499,15 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) {
// If V is a region, then add it to the worklist.
if (const MemRegion *R = V.getAsRegion()) {
AddToWorkList(R);
+
+ if (const auto *TVR = dyn_cast<TypedValueRegion>(R)) {
+ DefinedOrUnknownSVal RVS =
+ RM.getSValBuilder().getRegionValueSymbolVal(TVR);
+ if (const MemRegion *SR = RVS.getAsRegion()) {
+ AddToWorkList(SR);
+ }
+ }
+
SymReaper.markLive(R);
// All regions captured by a block are also live.
@@ -2480,34 +2521,37 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) {
// Update the set of live symbols.
- for (SymExpr::symbol_iterator SI = V.symbol_begin(), SE = V.symbol_end();
- SI!=SE; ++SI)
+ for (auto SI = V.symbol_begin(), SE = V.symbol_end(); SI != SE; ++SI) {
+ populateWorklistFromSymbol(*SI);
+
+ for (const auto *SD : ParentsToDerived[*SI])
+ populateWorklistFromSymbol(SD);
+
SymReaper.markLive(*SI);
+ }
}
-bool removeDeadBindingsWorker::UpdatePostponed() {
- // See if any postponed SymbolicRegions are actually live now, after
- // having done a scan.
- bool changed = false;
+void RemoveDeadBindingsWorker::populateWorklistFromSymbol(SymbolRef S) {
+ if (const auto *SD = dyn_cast<SymbolData>(S)) {
+ if (Loc::isLocType(SD->getType()) && !SymReaper.isLive(SD)) {
+ const SymbolicRegion *SR = RM.getRegionManager().getSymbolicRegion(SD);
- for (SmallVectorImpl<const SymbolicRegion*>::iterator
- I = Postponed.begin(), E = Postponed.end() ; I != E ; ++I) {
- if (const SymbolicRegion *SR = *I) {
- if (SymReaper.isLive(SR->getSymbol())) {
- changed |= AddToWorkList(SR);
- *I = nullptr;
- }
+ if (B.contains(SR))
+ AddToWorkList(SR);
+
+ const SymbolicRegion *SHR =
+ RM.getRegionManager().getSymbolicHeapRegion(SD);
+ if (B.contains(SHR))
+ AddToWorkList(SHR);
}
}
-
- return changed;
}
StoreRef RegionStoreManager::removeDeadBindings(Store store,
const StackFrameContext *LCtx,
SymbolReaper& SymReaper) {
RegionBindingsRef B = getRegionBindings(store);
- removeDeadBindingsWorker W(*this, StateMgr, B, SymReaper, LCtx);
+ RemoveDeadBindingsWorker W(*this, StateMgr, B, SymReaper, LCtx);
W.GenerateClusters();
// Enqueue the region roots onto the worklist.
@@ -2516,7 +2560,7 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store,
W.AddToWorkList(*I);
}
- do W.RunWorkList(); while (W.UpdatePostponed());
+ W.RunWorkList();
// We have now scanned the store, marking reachable regions and symbols
// as live. We now remove all the regions that are dead from the store
@@ -2525,24 +2569,9 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store,
const MemRegion *Base = I.getKey();
// If the cluster has been visited, we know the region has been marked.
- if (W.isVisited(Base))
- continue;
-
- // Remove the dead entry.
- B = B.remove(Base);
-
- if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(Base))
- SymReaper.maybeDead(SymR->getSymbol());
-
- // Mark all non-live symbols that this binding references as dead.
- const ClusterBindings &Cluster = I.getData();
- for (ClusterBindings::iterator CI = Cluster.begin(), CE = Cluster.end();
- CI != CE; ++CI) {
- SVal X = CI.getData();
- SymExpr::symbol_iterator SI = X.symbol_begin(), SE = X.symbol_end();
- for (; SI != SE; ++SI)
- SymReaper.maybeDead(*SI);
- }
+ // Otherwise, remove the dead entry.
+ if (!W.isVisited(Base))
+ B = B.remove(Base);
}
return StoreRef(B.asStore(), *this);
@@ -2553,7 +2582,7 @@ StoreRef RegionStoreManager::removeDeadBindings(Store store,
//===----------------------------------------------------------------------===//
void RegionStoreManager::print(Store store, raw_ostream &OS,
- const char* nl, const char *sep) {
+ const char* nl) {
RegionBindingsRef B = getRegionBindings(store);
OS << "Store (direct and default bindings), "
<< B.asStore()
diff --git a/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp b/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp
new file mode 100644
index 000000000000..2e40cc33381c
--- /dev/null
+++ b/lib/StaticAnalyzer/Core/RetainSummaryManager.cpp
@@ -0,0 +1,1229 @@
+//== RetainSummaryManager.cpp - Summaries for reference counting --*- 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 summaries implementation for retain counting, which
+// implements a reference count checker for Core Foundation, Cocoa
+// and OSObject (on Mac OS X).
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h"
+#include "clang/Analysis/DomainSpecific/CocoaConventions.h"
+#include "clang/AST/Attr.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/ParentMap.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+
+using namespace clang;
+using namespace ento;
+
+template <class T>
+constexpr static bool isOneOf() {
+ return false;
+}
+
+/// Helper function to check whether the class is one of the
+/// rest of varargs.
+template <class T, class P, class... ToCompare>
+constexpr static bool isOneOf() {
+ return std::is_same<T, P>::value || isOneOf<T, ToCompare...>();
+}
+
+namespace {
+
+/// Fake attribute class for RC* attributes.
+struct GeneralizedReturnsRetainedAttr {
+ static bool classof(const Attr *A) {
+ if (auto AA = dyn_cast<AnnotateAttr>(A))
+ return AA->getAnnotation() == "rc_ownership_returns_retained";
+ return false;
+ }
+};
+
+struct GeneralizedReturnsNotRetainedAttr {
+ static bool classof(const Attr *A) {
+ if (auto AA = dyn_cast<AnnotateAttr>(A))
+ return AA->getAnnotation() == "rc_ownership_returns_not_retained";
+ return false;
+ }
+};
+
+struct GeneralizedConsumedAttr {
+ static bool classof(const Attr *A) {
+ if (auto AA = dyn_cast<AnnotateAttr>(A))
+ return AA->getAnnotation() == "rc_ownership_consumed";
+ return false;
+ }
+};
+
+}
+
+template <class T>
+Optional<ObjKind> RetainSummaryManager::hasAnyEnabledAttrOf(const Decl *D,
+ QualType QT) {
+ ObjKind K;
+ if (isOneOf<T, CFConsumedAttr, CFReturnsRetainedAttr,
+ CFReturnsNotRetainedAttr>()) {
+ if (!TrackObjCAndCFObjects)
+ return None;
+
+ K = ObjKind::CF;
+ } else if (isOneOf<T, NSConsumedAttr, NSConsumesSelfAttr,
+ NSReturnsAutoreleasedAttr, NSReturnsRetainedAttr,
+ NSReturnsNotRetainedAttr, NSConsumesSelfAttr>()) {
+
+ if (!TrackObjCAndCFObjects)
+ return None;
+
+ if (isOneOf<T, NSReturnsRetainedAttr, NSReturnsAutoreleasedAttr,
+ NSReturnsNotRetainedAttr>() &&
+ !cocoa::isCocoaObjectRef(QT))
+ return None;
+ K = ObjKind::ObjC;
+ } else if (isOneOf<T, OSConsumedAttr, OSConsumesThisAttr,
+ OSReturnsNotRetainedAttr, OSReturnsRetainedAttr,
+ OSReturnsRetainedOnZeroAttr,
+ OSReturnsRetainedOnNonZeroAttr>()) {
+ if (!TrackOSObjects)
+ return None;
+ K = ObjKind::OS;
+ } else if (isOneOf<T, GeneralizedReturnsNotRetainedAttr,
+ GeneralizedReturnsRetainedAttr,
+ GeneralizedConsumedAttr>()) {
+ K = ObjKind::Generalized;
+ } else {
+ llvm_unreachable("Unexpected attribute");
+ }
+ if (D->hasAttr<T>())
+ return K;
+ return None;
+}
+
+template <class T1, class T2, class... Others>
+Optional<ObjKind> RetainSummaryManager::hasAnyEnabledAttrOf(const Decl *D,
+ QualType QT) {
+ if (auto Out = hasAnyEnabledAttrOf<T1>(D, QT))
+ return Out;
+ return hasAnyEnabledAttrOf<T2, Others...>(D, QT);
+}
+
+const RetainSummary *
+RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) {
+ // Unique "simple" summaries -- those without ArgEffects.
+ if (OldSumm.isSimple()) {
+ ::llvm::FoldingSetNodeID ID;
+ OldSumm.Profile(ID);
+
+ void *Pos;
+ CachedSummaryNode *N = SimpleSummaries.FindNodeOrInsertPos(ID, Pos);
+
+ if (!N) {
+ N = (CachedSummaryNode *) BPAlloc.Allocate<CachedSummaryNode>();
+ new (N) CachedSummaryNode(OldSumm);
+ SimpleSummaries.InsertNode(N, Pos);
+ }
+
+ return &N->getValue();
+ }
+
+ RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>();
+ new (Summ) RetainSummary(OldSumm);
+ return Summ;
+}
+
+static bool isSubclass(const Decl *D,
+ StringRef ClassName) {
+ using namespace ast_matchers;
+ DeclarationMatcher SubclassM = cxxRecordDecl(isSameOrDerivedFrom(ClassName));
+ return !(match(SubclassM, *D, D->getASTContext()).empty());
+}
+
+static bool isOSObjectSubclass(const Decl *D) {
+ return isSubclass(D, "OSObject");
+}
+
+static bool isOSObjectDynamicCast(StringRef S) {
+ return S == "safeMetaCast";
+}
+
+static bool isOSIteratorSubclass(const Decl *D) {
+ return isSubclass(D, "OSIterator");
+}
+
+static bool hasRCAnnotation(const Decl *D, StringRef rcAnnotation) {
+ for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) {
+ if (Ann->getAnnotation() == rcAnnotation)
+ return true;
+ }
+ return false;
+}
+
+static bool isRetain(const FunctionDecl *FD, StringRef FName) {
+ return FName.startswith_lower("retain") || FName.endswith_lower("retain");
+}
+
+static bool isRelease(const FunctionDecl *FD, StringRef FName) {
+ return FName.startswith_lower("release") || FName.endswith_lower("release");
+}
+
+static bool isAutorelease(const FunctionDecl *FD, StringRef FName) {
+ return FName.startswith_lower("autorelease") ||
+ FName.endswith_lower("autorelease");
+}
+
+static bool isMakeCollectable(StringRef FName) {
+ return FName.contains_lower("MakeCollectable");
+}
+
+/// A function is OSObject related if it is declared on a subclass
+/// of OSObject, or any of the parameters is a subclass of an OSObject.
+static bool isOSObjectRelated(const CXXMethodDecl *MD) {
+ if (isOSObjectSubclass(MD->getParent()))
+ return true;
+
+ for (ParmVarDecl *Param : MD->parameters()) {
+ QualType PT = Param->getType()->getPointeeType();
+ if (!PT.isNull())
+ if (CXXRecordDecl *RD = PT->getAsCXXRecordDecl())
+ if (isOSObjectSubclass(RD))
+ return true;
+ }
+
+ return false;
+}
+
+const RetainSummary *
+RetainSummaryManager::getSummaryForOSObject(const FunctionDecl *FD,
+ StringRef FName, QualType RetTy) {
+ if (RetTy->isPointerType()) {
+ const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl();
+ if (PD && isOSObjectSubclass(PD)) {
+ if (const IdentifierInfo *II = FD->getIdentifier()) {
+ if (isOSObjectDynamicCast(II->getName()))
+ return getDefaultSummary();
+
+ // All objects returned with functions *not* starting with
+ // get, or iterators, are returned at +1.
+ if ((!II->getName().startswith("get") &&
+ !II->getName().startswith("Get")) ||
+ isOSIteratorSubclass(PD)) {
+ return getOSSummaryCreateRule(FD);
+ } else {
+ return getOSSummaryGetRule(FD);
+ }
+ }
+ }
+ }
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+ const CXXRecordDecl *Parent = MD->getParent();
+ if (TrackOSObjects && Parent && isOSObjectSubclass(Parent)) {
+ if (FName == "release")
+ return getOSSummaryReleaseRule(FD);
+
+ if (FName == "retain")
+ return getOSSummaryRetainRule(FD);
+
+ if (FName == "free")
+ return getOSSummaryFreeRule(FD);
+
+ if (MD->getOverloadedOperator() == OO_New)
+ return getOSSummaryCreateRule(MD);
+ }
+ }
+
+ return nullptr;
+}
+
+const RetainSummary *RetainSummaryManager::getSummaryForObjCOrCFObject(
+ const FunctionDecl *FD,
+ StringRef FName,
+ QualType RetTy,
+ const FunctionType *FT,
+ bool &AllowAnnotations) {
+
+ ArgEffects ScratchArgs(AF.getEmptyMap());
+
+ std::string RetTyName = RetTy.getAsString();
+ if (FName == "pthread_create" || FName == "pthread_setspecific") {
+ // Part of: <rdar://problem/7299394> and <rdar://problem/11282706>.
+ // This will be addressed better with IPA.
+ return getPersistentStopSummary();
+ } else if(FName == "NSMakeCollectable") {
+ // Handle: id NSMakeCollectable(CFTypeRef)
+ AllowAnnotations = false;
+ return RetTy->isObjCIdType() ? getUnarySummary(FT, DoNothing)
+ : getPersistentStopSummary();
+ } else if (FName == "CMBufferQueueDequeueAndRetain" ||
+ FName == "CMBufferQueueDequeueIfDataReadyAndRetain") {
+ // Part of: <rdar://problem/39390714>.
+ return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF),
+ ScratchArgs,
+ ArgEffect(DoNothing),
+ ArgEffect(DoNothing));
+ } else if (FName == "CFPlugInInstanceCreate") {
+ return getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs);
+ } else if (FName == "IORegistryEntrySearchCFProperty" ||
+ (RetTyName == "CFMutableDictionaryRef" &&
+ (FName == "IOBSDNameMatching" || FName == "IOServiceMatching" ||
+ FName == "IOServiceNameMatching" ||
+ FName == "IORegistryEntryIDMatching" ||
+ FName == "IOOpenFirmwarePathMatching"))) {
+ // Part of <rdar://problem/6961230>. (IOKit)
+ // This should be addressed using a API table.
+ return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "IOServiceGetMatchingService" ||
+ FName == "IOServiceGetMatchingServices") {
+ // FIXES: <rdar://problem/6326900>
+ // This should be addressed using a API table. This strcmp is also
+ // a little gross, but there is no need to super optimize here.
+ ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(DecRef, ObjKind::CF));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "IOServiceAddNotification" ||
+ FName == "IOServiceAddMatchingNotification") {
+ // Part of <rdar://problem/6961230>. (IOKit)
+ // This should be addressed using a API table.
+ ScratchArgs = AF.add(ScratchArgs, 2, ArgEffect(DecRef, ObjKind::CF));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "CVPixelBufferCreateWithBytes") {
+ // FIXES: <rdar://problem/7283567>
+ // Eventually this can be improved by recognizing that the pixel
+ // buffer passed to CVPixelBufferCreateWithBytes is released via
+ // a callback and doing full IPA to make sure this is done correctly.
+ // FIXME: This function has an out parameter that returns an
+ // allocated object.
+ ScratchArgs = AF.add(ScratchArgs, 7, ArgEffect(StopTracking));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "CGBitmapContextCreateWithData") {
+ // FIXES: <rdar://problem/7358899>
+ // Eventually this can be improved by recognizing that 'releaseInfo'
+ // passed to CGBitmapContextCreateWithData is released via
+ // a callback and doing full IPA to make sure this is done correctly.
+ ScratchArgs = AF.add(ScratchArgs, 8, ArgEffect(ArgEffect(StopTracking)));
+ return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "CVPixelBufferCreateWithPlanarBytes") {
+ // FIXES: <rdar://problem/7283567>
+ // Eventually this can be improved by recognizing that the pixel
+ // buffer passed to CVPixelBufferCreateWithPlanarBytes is released
+ // via a callback and doing full IPA to make sure this is done
+ // correctly.
+ ScratchArgs = AF.add(ScratchArgs, 12, ArgEffect(StopTracking));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "VTCompressionSessionEncodeFrame") {
+ // The context argument passed to VTCompressionSessionEncodeFrame()
+ // is passed to the callback specified when creating the session
+ // (e.g. with VTCompressionSessionCreate()) which can release it.
+ // To account for this possibility, conservatively stop tracking
+ // the context.
+ ScratchArgs = AF.add(ScratchArgs, 5, ArgEffect(StopTracking));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName == "dispatch_set_context" ||
+ FName == "xpc_connection_set_context") {
+ // <rdar://problem/11059275> - The analyzer currently doesn't have
+ // a good way to reason about the finalizer function for libdispatch.
+ // If we pass a context object that is memory managed, stop tracking it.
+ // <rdar://problem/13783514> - Same problem, but for XPC.
+ // FIXME: this hack should possibly go away once we can handle
+ // libdispatch and XPC finalizers.
+ ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(StopTracking));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+ } else if (FName.startswith("NSLog")) {
+ return getDoNothingSummary();
+ } else if (FName.startswith("NS") &&
+ (FName.find("Insert") != StringRef::npos)) {
+ // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
+ // be deallocated by NSMapRemove. (radar://11152419)
+ ScratchArgs = AF.add(ScratchArgs, 1, ArgEffect(StopTracking));
+ ScratchArgs = AF.add(ScratchArgs, 2, ArgEffect(StopTracking));
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs, ArgEffect(DoNothing),
+ ArgEffect(DoNothing));
+ }
+
+ if (RetTy->isPointerType()) {
+
+ // For CoreFoundation ('CF') types.
+ if (cocoa::isRefType(RetTy, "CF", FName)) {
+ if (isRetain(FD, FName)) {
+ // CFRetain isn't supposed to be annotated. However, this may as
+ // well be a user-made "safe" CFRetain function that is incorrectly
+ // annotated as cf_returns_retained due to lack of better options.
+ // We want to ignore such annotation.
+ AllowAnnotations = false;
+
+ return getUnarySummary(FT, IncRef);
+ } else if (isAutorelease(FD, FName)) {
+ // The headers use cf_consumed, but we can fully model CFAutorelease
+ // ourselves.
+ AllowAnnotations = false;
+
+ return getUnarySummary(FT, Autorelease);
+ } else if (isMakeCollectable(FName)) {
+ AllowAnnotations = false;
+ return getUnarySummary(FT, DoNothing);
+ } else {
+ return getCFCreateGetRuleSummary(FD);
+ }
+ }
+
+ // For CoreGraphics ('CG') and CoreVideo ('CV') types.
+ if (cocoa::isRefType(RetTy, "CG", FName) ||
+ cocoa::isRefType(RetTy, "CV", FName)) {
+ if (isRetain(FD, FName))
+ return getUnarySummary(FT, IncRef);
+ else
+ return getCFCreateGetRuleSummary(FD);
+ }
+
+ // For all other CF-style types, use the Create/Get
+ // rule for summaries but don't support Retain functions
+ // with framework-specific prefixes.
+ if (coreFoundation::isCFObjectRef(RetTy)) {
+ return getCFCreateGetRuleSummary(FD);
+ }
+
+ if (FD->hasAttr<CFAuditedTransferAttr>()) {
+ return getCFCreateGetRuleSummary(FD);
+ }
+ }
+
+ // Check for release functions, the only kind of functions that we care
+ // about that don't return a pointer type.
+ if (FName.startswith("CG") || FName.startswith("CF")) {
+ // Test for 'CGCF'.
+ FName = FName.substr(FName.startswith("CGCF") ? 4 : 2);
+
+ if (isRelease(FD, FName))
+ return getUnarySummary(FT, DecRef);
+ else {
+ assert(ScratchArgs.isEmpty());
+ // Remaining CoreFoundation and CoreGraphics functions.
+ // We use to assume that they all strictly followed the ownership idiom
+ // and that ownership cannot be transferred. While this is technically
+ // correct, many methods allow a tracked object to escape. For example:
+ //
+ // CFMutableDictionaryRef x = CFDictionaryCreateMutable(...);
+ // CFDictionaryAddValue(y, key, x);
+ // CFRelease(x);
+ // ... it is okay to use 'x' since 'y' has a reference to it
+ //
+ // We handle this and similar cases with the follow heuristic. If the
+ // function name contains "InsertValue", "SetValue", "AddValue",
+ // "AppendValue", or "SetAttribute", then we assume that arguments may
+ // "escape." This means that something else holds on to the object,
+ // allowing it be used even after its local retain count drops to 0.
+ ArgEffectKind E =
+ (StrInStrNoCase(FName, "InsertValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "AppendValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "SetAttribute") != StringRef::npos)
+ ? MayEscape
+ : DoNothing;
+
+ return getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(E, ObjKind::CF));
+ }
+ }
+
+ return nullptr;
+}
+
+const RetainSummary *
+RetainSummaryManager::generateSummary(const FunctionDecl *FD,
+ bool &AllowAnnotations) {
+ // We generate "stop" summaries for implicitly defined functions.
+ if (FD->isImplicit())
+ return getPersistentStopSummary();
+
+ const IdentifierInfo *II = FD->getIdentifier();
+
+ StringRef FName = II ? II->getName() : "";
+
+ // Strip away preceding '_'. Doing this here will effect all the checks
+ // down below.
+ FName = FName.substr(FName.find_first_not_of('_'));
+
+ // Inspect the result type. Strip away any typedefs.
+ const auto *FT = FD->getType()->getAs<FunctionType>();
+ QualType RetTy = FT->getReturnType();
+
+ if (TrackOSObjects)
+ if (const RetainSummary *S = getSummaryForOSObject(FD, FName, RetTy))
+ return S;
+
+ if (TrackObjCAndCFObjects)
+ if (const RetainSummary *S =
+ getSummaryForObjCOrCFObject(FD, FName, RetTy, FT, AllowAnnotations))
+ return S;
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD))
+ if (!(TrackOSObjects && isOSObjectRelated(MD)))
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ArgEffects(AF.getEmptyMap()),
+ ArgEffect(DoNothing),
+ ArgEffect(StopTracking),
+ ArgEffect(DoNothing));
+
+ return getDefaultSummary();
+}
+
+const RetainSummary *
+RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) {
+ // If we don't know what function we're calling, use our default summary.
+ if (!FD)
+ return getDefaultSummary();
+
+ // Look up a summary in our cache of FunctionDecls -> Summaries.
+ FuncSummariesTy::iterator I = FuncSummaries.find(FD);
+ if (I != FuncSummaries.end())
+ return I->second;
+
+ // No summary? Generate one.
+ bool AllowAnnotations = true;
+ const RetainSummary *S = generateSummary(FD, AllowAnnotations);
+
+ // Annotations override defaults.
+ if (AllowAnnotations)
+ updateSummaryFromAnnotations(S, FD);
+
+ FuncSummaries[FD] = S;
+ return S;
+}
+
+//===----------------------------------------------------------------------===//
+// Summary creation for functions (largely uses of Core Foundation).
+//===----------------------------------------------------------------------===//
+
+static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) {
+ switch (E.getKind()) {
+ case DoNothing:
+ case Autorelease:
+ case DecRefBridgedTransferred:
+ case IncRef:
+ case UnretainedOutParameter:
+ case RetainedOutParameter:
+ case RetainedOutParameterOnZero:
+ case RetainedOutParameterOnNonZero:
+ case MayEscape:
+ case StopTracking:
+ case StopTrackingHard:
+ return E.withKind(StopTrackingHard);
+ case DecRef:
+ case DecRefAndStopTrackingHard:
+ return E.withKind(DecRefAndStopTrackingHard);
+ case Dealloc:
+ return E.withKind(Dealloc);
+ }
+
+ llvm_unreachable("Unknown ArgEffect kind");
+}
+
+void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S,
+ const CallEvent &Call) {
+ if (Call.hasNonZeroCallbackArg()) {
+ ArgEffect RecEffect =
+ getStopTrackingHardEquivalent(S->getReceiverEffect());
+ ArgEffect DefEffect =
+ getStopTrackingHardEquivalent(S->getDefaultArgEffect());
+
+ ArgEffects ScratchArgs(AF.getEmptyMap());
+ ArgEffects CustomArgEffects = S->getArgEffects();
+ for (ArgEffects::iterator I = CustomArgEffects.begin(),
+ E = CustomArgEffects.end();
+ I != E; ++I) {
+ ArgEffect Translated = getStopTrackingHardEquivalent(I->second);
+ if (Translated.getKind() != DefEffect.getKind())
+ ScratchArgs = AF.add(ScratchArgs, I->first, Translated);
+ }
+
+ RetEffect RE = RetEffect::MakeNoRetHard();
+
+ // Special cases where the callback argument CANNOT free the return value.
+ // This can generally only happen if we know that the callback will only be
+ // called when the return value is already being deallocated.
+ if (const SimpleFunctionCall *FC = dyn_cast<SimpleFunctionCall>(&Call)) {
+ if (IdentifierInfo *Name = FC->getDecl()->getIdentifier()) {
+ // When the CGBitmapContext is deallocated, the callback here will free
+ // the associated data buffer.
+ // The callback in dispatch_data_create frees the buffer, but not
+ // the data object.
+ if (Name->isStr("CGBitmapContextCreateWithData") ||
+ Name->isStr("dispatch_data_create"))
+ RE = S->getRetEffect();
+ }
+ }
+
+ S = getPersistentSummary(RE, ScratchArgs, RecEffect, DefEffect);
+ }
+
+ // Special case '[super init];' and '[self init];'
+ //
+ // Even though calling '[super init]' without assigning the result to self
+ // and checking if the parent returns 'nil' is a bad pattern, it is common.
+ // Additionally, our Self Init checker already warns about it. To avoid
+ // overwhelming the user with messages from both checkers, we model the case
+ // of '[super init]' in cases when it is not consumed by another expression
+ // as if the call preserves the value of 'self'; essentially, assuming it can
+ // never fail and return 'nil'.
+ // Note, we don't want to just stop tracking the value since we want the
+ // RetainCount checker to report leaks and use-after-free if SelfInit checker
+ // is turned off.
+ if (const ObjCMethodCall *MC = dyn_cast<ObjCMethodCall>(&Call)) {
+ if (MC->getMethodFamily() == OMF_init && MC->isReceiverSelfOrSuper()) {
+
+ // Check if the message is not consumed, we know it will not be used in
+ // an assignment, ex: "self = [super init]".
+ const Expr *ME = MC->getOriginExpr();
+ const LocationContext *LCtx = MC->getLocationContext();
+ ParentMap &PM = LCtx->getAnalysisDeclContext()->getParentMap();
+ if (!PM.isConsumedExpr(ME)) {
+ RetainSummaryTemplate ModifiableSummaryTemplate(S, *this);
+ ModifiableSummaryTemplate->setReceiverEffect(ArgEffect(DoNothing));
+ ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet());
+ }
+ }
+ }
+}
+
+const RetainSummary *
+RetainSummaryManager::getSummary(const CallEvent &Call,
+ QualType ReceiverType) {
+ const RetainSummary *Summ;
+ switch (Call.getKind()) {
+ case CE_Function:
+ case CE_CXXMember:
+ case CE_CXXMemberOperator:
+ case CE_CXXConstructor:
+ case CE_CXXAllocator:
+ Summ = getFunctionSummary(cast_or_null<FunctionDecl>(Call.getDecl()));
+ break;
+ case CE_Block:
+ case CE_CXXDestructor:
+ // FIXME: These calls are currently unsupported.
+ return getPersistentStopSummary();
+ case CE_ObjCMessage: {
+ const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call);
+ if (Msg.isInstanceMessage())
+ Summ = getInstanceMethodSummary(Msg, ReceiverType);
+ else
+ Summ = getClassMethodSummary(Msg);
+ break;
+ }
+ }
+
+ updateSummaryForCall(Summ, Call);
+
+ assert(Summ && "Unknown call type?");
+ return Summ;
+}
+
+
+const RetainSummary *
+RetainSummaryManager::getCFCreateGetRuleSummary(const FunctionDecl *FD) {
+ if (coreFoundation::followsCreateRule(FD))
+ return getCFSummaryCreateRule(FD);
+
+ return getCFSummaryGetRule(FD);
+}
+
+bool RetainSummaryManager::isTrustedReferenceCountImplementation(
+ const FunctionDecl *FD) {
+ return hasRCAnnotation(FD, "rc_ownership_trusted_implementation");
+}
+
+Optional<RetainSummaryManager::BehaviorSummary>
+RetainSummaryManager::canEval(const CallExpr *CE, const FunctionDecl *FD,
+ bool &hasTrustedImplementationAnnotation) {
+
+ IdentifierInfo *II = FD->getIdentifier();
+ if (!II)
+ return None;
+
+ StringRef FName = II->getName();
+ FName = FName.substr(FName.find_first_not_of('_'));
+
+ QualType ResultTy = CE->getCallReturnType(Ctx);
+ if (ResultTy->isObjCIdType()) {
+ if (II->isStr("NSMakeCollectable"))
+ return BehaviorSummary::Identity;
+ } else if (ResultTy->isPointerType()) {
+ // Handle: (CF|CG|CV)Retain
+ // CFAutorelease
+ // It's okay to be a little sloppy here.
+ if (FName == "CMBufferQueueDequeueAndRetain" ||
+ FName == "CMBufferQueueDequeueIfDataReadyAndRetain") {
+ // Part of: <rdar://problem/39390714>.
+ // These are not retain. They just return something and retain it.
+ return None;
+ }
+ if (cocoa::isRefType(ResultTy, "CF", FName) ||
+ cocoa::isRefType(ResultTy, "CG", FName) ||
+ cocoa::isRefType(ResultTy, "CV", FName))
+ if (isRetain(FD, FName) || isAutorelease(FD, FName) ||
+ isMakeCollectable(FName))
+ return BehaviorSummary::Identity;
+
+ // safeMetaCast is called by OSDynamicCast.
+ // We assume that OSDynamicCast is either an identity (cast is OK,
+ // the input was non-zero),
+ // or that it returns zero (when the cast failed, or the input
+ // was zero).
+ if (TrackOSObjects && isOSObjectDynamicCast(FName)) {
+ return BehaviorSummary::IdentityOrZero;
+ }
+
+ const FunctionDecl* FDD = FD->getDefinition();
+ if (FDD && isTrustedReferenceCountImplementation(FDD)) {
+ hasTrustedImplementationAnnotation = true;
+ return BehaviorSummary::Identity;
+ }
+ }
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+ const CXXRecordDecl *Parent = MD->getParent();
+ if (TrackOSObjects && Parent && isOSObjectSubclass(Parent))
+ if (FName == "release" || FName == "retain")
+ return BehaviorSummary::NoOp;
+ }
+
+ return None;
+}
+
+const RetainSummary *
+RetainSummaryManager::getUnarySummary(const FunctionType* FT,
+ ArgEffectKind AE) {
+
+ // Unary functions have no arg effects by definition.
+ ArgEffects ScratchArgs(AF.getEmptyMap());
+
+ // Sanity check that this is *really* a unary function. This can
+ // happen if people do weird things.
+ const FunctionProtoType* FTP = dyn_cast<FunctionProtoType>(FT);
+ if (!FTP || FTP->getNumParams() != 1)
+ return getPersistentStopSummary();
+
+ ArgEffect Effect(AE, ObjKind::CF);
+
+ ScratchArgs = AF.add(ScratchArgs, 0, Effect);
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ ScratchArgs,
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+}
+
+const RetainSummary *
+RetainSummaryManager::getOSSummaryRetainRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ AF.getEmptyMap(),
+ /*ReceiverEff=*/ArgEffect(DoNothing),
+ /*DefaultEff=*/ArgEffect(DoNothing),
+ /*ThisEff=*/ArgEffect(IncRef, ObjKind::OS));
+}
+
+const RetainSummary *
+RetainSummaryManager::getOSSummaryReleaseRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ AF.getEmptyMap(),
+ /*ReceiverEff=*/ArgEffect(DoNothing),
+ /*DefaultEff=*/ArgEffect(DoNothing),
+ /*ThisEff=*/ArgEffect(DecRef, ObjKind::OS));
+}
+
+const RetainSummary *
+RetainSummaryManager::getOSSummaryFreeRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeNoRet(),
+ AF.getEmptyMap(),
+ /*ReceiverEff=*/ArgEffect(DoNothing),
+ /*DefaultEff=*/ArgEffect(DoNothing),
+ /*ThisEff=*/ArgEffect(Dealloc, ObjKind::OS));
+}
+
+const RetainSummary *
+RetainSummaryManager::getOSSummaryCreateRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeOwned(ObjKind::OS),
+ AF.getEmptyMap());
+}
+
+const RetainSummary *
+RetainSummaryManager::getOSSummaryGetRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::OS),
+ AF.getEmptyMap());
+}
+
+const RetainSummary *
+RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF),
+ ArgEffects(AF.getEmptyMap()));
+}
+
+const RetainSummary *
+RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) {
+ return getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::CF),
+ ArgEffects(AF.getEmptyMap()),
+ ArgEffect(DoNothing), ArgEffect(DoNothing));
+}
+
+
+
+
+//===----------------------------------------------------------------------===//
+// Summary creation for Selectors.
+//===----------------------------------------------------------------------===//
+
+Optional<RetEffect>
+RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy,
+ const Decl *D) {
+ if (hasAnyEnabledAttrOf<NSReturnsRetainedAttr>(D, RetTy))
+ return ObjCAllocRetE;
+
+ if (auto K = hasAnyEnabledAttrOf<CFReturnsRetainedAttr, OSReturnsRetainedAttr,
+ GeneralizedReturnsRetainedAttr>(D, RetTy))
+ return RetEffect::MakeOwned(*K);
+
+ if (auto K = hasAnyEnabledAttrOf<
+ CFReturnsNotRetainedAttr, OSReturnsNotRetainedAttr,
+ GeneralizedReturnsNotRetainedAttr, NSReturnsNotRetainedAttr,
+ NSReturnsAutoreleasedAttr>(D, RetTy))
+ return RetEffect::MakeNotOwned(*K);
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(D))
+ for (const auto *PD : MD->overridden_methods())
+ if (auto RE = getRetEffectFromAnnotations(RetTy, PD))
+ return RE;
+
+ return None;
+}
+
+/// \return Whether the chain of typedefs starting from {@code QT}
+/// has a typedef with a given name {@code Name}.
+static bool hasTypedefNamed(QualType QT,
+ StringRef Name) {
+ while (auto *T = dyn_cast<TypedefType>(QT)) {
+ const auto &Context = T->getDecl()->getASTContext();
+ if (T->getDecl()->getIdentifier() == &Context.Idents.get(Name))
+ return true;
+ QT = T->getDecl()->getUnderlyingType();
+ }
+ return false;
+}
+
+static QualType getCallableReturnType(const NamedDecl *ND) {
+ if (const auto *FD = dyn_cast<FunctionDecl>(ND)) {
+ return FD->getReturnType();
+ } else if (const auto *MD = dyn_cast<ObjCMethodDecl>(ND)) {
+ return MD->getReturnType();
+ } else {
+ llvm_unreachable("Unexpected decl");
+ }
+}
+
+bool RetainSummaryManager::applyParamAnnotationEffect(
+ const ParmVarDecl *pd, unsigned parm_idx, const NamedDecl *FD,
+ RetainSummaryTemplate &Template) {
+ QualType QT = pd->getType();
+ if (auto K =
+ hasAnyEnabledAttrOf<NSConsumedAttr, CFConsumedAttr, OSConsumedAttr,
+ GeneralizedConsumedAttr>(pd, QT)) {
+ Template->addArg(AF, parm_idx, ArgEffect(DecRef, *K));
+ return true;
+ } else if (auto K = hasAnyEnabledAttrOf<
+ CFReturnsRetainedAttr, OSReturnsRetainedAttr,
+ OSReturnsRetainedOnNonZeroAttr, OSReturnsRetainedOnZeroAttr,
+ GeneralizedReturnsRetainedAttr>(pd, QT)) {
+
+ // For OSObjects, we try to guess whether the object is created based
+ // on the return value.
+ if (K == ObjKind::OS) {
+ QualType QT = getCallableReturnType(FD);
+
+ bool HasRetainedOnZero = pd->hasAttr<OSReturnsRetainedOnZeroAttr>();
+ bool HasRetainedOnNonZero = pd->hasAttr<OSReturnsRetainedOnNonZeroAttr>();
+
+ // The usual convention is to create an object on non-zero return, but
+ // it's reverted if the typedef chain has a typedef kern_return_t,
+ // because kReturnSuccess constant is defined as zero.
+ // The convention can be overwritten by custom attributes.
+ bool SuccessOnZero =
+ HasRetainedOnZero ||
+ (hasTypedefNamed(QT, "kern_return_t") && !HasRetainedOnNonZero);
+ bool ShouldSplit = !QT.isNull() && !QT->isVoidType();
+ ArgEffectKind AK = RetainedOutParameter;
+ if (ShouldSplit && SuccessOnZero) {
+ AK = RetainedOutParameterOnZero;
+ } else if (ShouldSplit && (!SuccessOnZero || HasRetainedOnNonZero)) {
+ AK = RetainedOutParameterOnNonZero;
+ }
+ Template->addArg(AF, parm_idx, ArgEffect(AK, ObjKind::OS));
+ }
+
+ // For others:
+ // Do nothing. Retained out parameters will either point to a +1 reference
+ // or NULL, but the way you check for failure differs depending on the
+ // API. Consequently, we don't have a good way to track them yet.
+ return true;
+ } else if (auto K = hasAnyEnabledAttrOf<CFReturnsNotRetainedAttr,
+ OSReturnsNotRetainedAttr,
+ GeneralizedReturnsNotRetainedAttr>(
+ pd, QT)) {
+ Template->addArg(AF, parm_idx, ArgEffect(UnretainedOutParameter, *K));
+ return true;
+ }
+
+ if (const auto *MD = dyn_cast<CXXMethodDecl>(FD)) {
+ for (const auto *OD : MD->overridden_methods()) {
+ const ParmVarDecl *OP = OD->parameters()[parm_idx];
+ if (applyParamAnnotationEffect(OP, parm_idx, OD, Template))
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void
+RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
+ const FunctionDecl *FD) {
+ if (!FD)
+ return;
+
+ assert(Summ && "Must have a summary to add annotations to.");
+ RetainSummaryTemplate Template(Summ, *this);
+
+ // Effects on the parameters.
+ unsigned parm_idx = 0;
+ for (auto pi = FD->param_begin(),
+ pe = FD->param_end(); pi != pe; ++pi, ++parm_idx)
+ applyParamAnnotationEffect(*pi, parm_idx, FD, Template);
+
+ QualType RetTy = FD->getReturnType();
+ if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD))
+ Template->setRetEffect(*RetE);
+
+ if (hasAnyEnabledAttrOf<OSConsumesThisAttr>(FD, RetTy))
+ Template->setThisEffect(ArgEffect(DecRef, ObjKind::OS));
+}
+
+void
+RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ,
+ const ObjCMethodDecl *MD) {
+ if (!MD)
+ return;
+
+ assert(Summ && "Must have a valid summary to add annotations to");
+ RetainSummaryTemplate Template(Summ, *this);
+
+ // Effects on the receiver.
+ if (hasAnyEnabledAttrOf<NSConsumesSelfAttr>(MD, MD->getReturnType()))
+ Template->setReceiverEffect(ArgEffect(DecRef, ObjKind::ObjC));
+
+ // Effects on the parameters.
+ unsigned parm_idx = 0;
+ for (auto pi = MD->param_begin(), pe = MD->param_end(); pi != pe;
+ ++pi, ++parm_idx)
+ applyParamAnnotationEffect(*pi, parm_idx, MD, Template);
+
+ QualType RetTy = MD->getReturnType();
+ if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, MD))
+ Template->setRetEffect(*RetE);
+}
+
+const RetainSummary *
+RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD,
+ Selector S, QualType RetTy) {
+ // Any special effects?
+ ArgEffect ReceiverEff = ArgEffect(DoNothing, ObjKind::ObjC);
+ RetEffect ResultEff = RetEffect::MakeNoRet();
+
+ // Check the method family, and apply any default annotations.
+ switch (MD ? MD->getMethodFamily() : S.getMethodFamily()) {
+ case OMF_None:
+ case OMF_initialize:
+ case OMF_performSelector:
+ // Assume all Objective-C methods follow Cocoa Memory Management rules.
+ // FIXME: Does the non-threaded performSelector family really belong here?
+ // The selector could be, say, @selector(copy).
+ if (cocoa::isCocoaObjectRef(RetTy))
+ ResultEff = RetEffect::MakeNotOwned(ObjKind::ObjC);
+ else if (coreFoundation::isCFObjectRef(RetTy)) {
+ // ObjCMethodDecl currently doesn't consider CF objects as valid return
+ // values for alloc, new, copy, or mutableCopy, so we have to
+ // double-check with the selector. This is ugly, but there aren't that
+ // many Objective-C methods that return CF objects, right?
+ if (MD) {
+ switch (S.getMethodFamily()) {
+ case OMF_alloc:
+ case OMF_new:
+ case OMF_copy:
+ case OMF_mutableCopy:
+ ResultEff = RetEffect::MakeOwned(ObjKind::CF);
+ break;
+ default:
+ ResultEff = RetEffect::MakeNotOwned(ObjKind::CF);
+ break;
+ }
+ } else {
+ ResultEff = RetEffect::MakeNotOwned(ObjKind::CF);
+ }
+ }
+ break;
+ case OMF_init:
+ ResultEff = ObjCInitRetE;
+ ReceiverEff = ArgEffect(DecRef, ObjKind::ObjC);
+ break;
+ case OMF_alloc:
+ case OMF_new:
+ case OMF_copy:
+ case OMF_mutableCopy:
+ if (cocoa::isCocoaObjectRef(RetTy))
+ ResultEff = ObjCAllocRetE;
+ else if (coreFoundation::isCFObjectRef(RetTy))
+ ResultEff = RetEffect::MakeOwned(ObjKind::CF);
+ break;
+ case OMF_autorelease:
+ ReceiverEff = ArgEffect(Autorelease, ObjKind::ObjC);
+ break;
+ case OMF_retain:
+ ReceiverEff = ArgEffect(IncRef, ObjKind::ObjC);
+ break;
+ case OMF_release:
+ ReceiverEff = ArgEffect(DecRef, ObjKind::ObjC);
+ break;
+ case OMF_dealloc:
+ ReceiverEff = ArgEffect(Dealloc, ObjKind::ObjC);
+ break;
+ case OMF_self:
+ // -self is handled specially by the ExprEngine to propagate the receiver.
+ break;
+ case OMF_retainCount:
+ case OMF_finalize:
+ // These methods don't return objects.
+ break;
+ }
+
+ // If one of the arguments in the selector has the keyword 'delegate' we
+ // should stop tracking the reference count for the receiver. This is
+ // because the reference count is quite possibly handled by a delegate
+ // method.
+ if (S.isKeywordSelector()) {
+ for (unsigned i = 0, e = S.getNumArgs(); i != e; ++i) {
+ StringRef Slot = S.getNameForSlot(i);
+ if (Slot.substr(Slot.size() - 8).equals_lower("delegate")) {
+ if (ResultEff == ObjCInitRetE)
+ ResultEff = RetEffect::MakeNoRetHard();
+ else
+ ReceiverEff = ArgEffect(StopTrackingHard, ObjKind::ObjC);
+ }
+ }
+ }
+
+ if (ReceiverEff.getKind() == DoNothing &&
+ ResultEff.getKind() == RetEffect::NoRet)
+ return getDefaultSummary();
+
+ return getPersistentSummary(ResultEff, ArgEffects(AF.getEmptyMap()),
+ ArgEffect(ReceiverEff), ArgEffect(MayEscape));
+}
+
+const RetainSummary *RetainSummaryManager::getInstanceMethodSummary(
+ const ObjCMethodCall &Msg,
+ QualType ReceiverType) {
+ const ObjCInterfaceDecl *ReceiverClass = nullptr;
+
+ // We do better tracking of the type of the object than the core ExprEngine.
+ // See if we have its type in our private state.
+ if (!ReceiverType.isNull())
+ if (const auto *PT = ReceiverType->getAs<ObjCObjectPointerType>())
+ ReceiverClass = PT->getInterfaceDecl();
+
+ // If we don't know what kind of object this is, fall back to its static type.
+ if (!ReceiverClass)
+ ReceiverClass = Msg.getReceiverInterface();
+
+ // FIXME: The receiver could be a reference to a class, meaning that
+ // we should use the class method.
+ // id x = [NSObject class];
+ // [x performSelector:... withObject:... afterDelay:...];
+ Selector S = Msg.getSelector();
+ const ObjCMethodDecl *Method = Msg.getDecl();
+ if (!Method && ReceiverClass)
+ Method = ReceiverClass->getInstanceMethod(S);
+
+ return getMethodSummary(S, ReceiverClass, Method, Msg.getResultType(),
+ ObjCMethodSummaries);
+}
+
+const RetainSummary *
+RetainSummaryManager::getMethodSummary(Selector S,
+ const ObjCInterfaceDecl *ID,
+ const ObjCMethodDecl *MD, QualType RetTy,
+ ObjCMethodSummariesTy &CachedSummaries) {
+
+ // Objective-C method summaries are only applicable to ObjC and CF objects.
+ if (!TrackObjCAndCFObjects)
+ return getDefaultSummary();
+
+ // Look up a summary in our summary cache.
+ const RetainSummary *Summ = CachedSummaries.find(ID, S);
+
+ if (!Summ) {
+ Summ = getStandardMethodSummary(MD, S, RetTy);
+
+ // Annotations override defaults.
+ updateSummaryFromAnnotations(Summ, MD);
+
+ // Memoize the summary.
+ CachedSummaries[ObjCSummaryKey(ID, S)] = Summ;
+ }
+
+ return Summ;
+}
+
+void RetainSummaryManager::InitializeClassMethodSummaries() {
+ ArgEffects ScratchArgs = AF.getEmptyMap();
+
+ // Create the [NSAssertionHandler currentHander] summary.
+ addClassMethSummary("NSAssertionHandler", "currentHandler",
+ getPersistentSummary(RetEffect::MakeNotOwned(ObjKind::ObjC),
+ ScratchArgs));
+
+ // Create the [NSAutoreleasePool addObject:] summary.
+ ScratchArgs = AF.add(ScratchArgs, 0, ArgEffect(Autorelease));
+ addClassMethSummary("NSAutoreleasePool", "addObject",
+ getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs,
+ ArgEffect(DoNothing),
+ ArgEffect(Autorelease)));
+}
+
+void RetainSummaryManager::InitializeMethodSummaries() {
+
+ ArgEffects ScratchArgs = AF.getEmptyMap();
+ // Create the "init" selector. It just acts as a pass-through for the
+ // receiver.
+ const RetainSummary *InitSumm = getPersistentSummary(
+ ObjCInitRetE, ScratchArgs, ArgEffect(DecRef, ObjKind::ObjC));
+ addNSObjectMethSummary(GetNullarySelector("init", Ctx), InitSumm);
+
+ // awakeAfterUsingCoder: behaves basically like an 'init' method. It
+ // claims the receiver and returns a retained object.
+ addNSObjectMethSummary(GetUnarySelector("awakeAfterUsingCoder", Ctx),
+ InitSumm);
+
+ // The next methods are allocators.
+ const RetainSummary *AllocSumm = getPersistentSummary(ObjCAllocRetE,
+ ScratchArgs);
+ const RetainSummary *CFAllocSumm =
+ getPersistentSummary(RetEffect::MakeOwned(ObjKind::CF), ScratchArgs);
+
+ // Create the "retain" selector.
+ RetEffect NoRet = RetEffect::MakeNoRet();
+ const RetainSummary *Summ = getPersistentSummary(
+ NoRet, ScratchArgs, ArgEffect(IncRef, ObjKind::ObjC));
+ addNSObjectMethSummary(GetNullarySelector("retain", Ctx), Summ);
+
+ // Create the "release" selector.
+ Summ = getPersistentSummary(NoRet, ScratchArgs,
+ ArgEffect(DecRef, ObjKind::ObjC));
+ addNSObjectMethSummary(GetNullarySelector("release", Ctx), Summ);
+
+ // Create the -dealloc summary.
+ Summ = getPersistentSummary(NoRet, ScratchArgs, ArgEffect(Dealloc,
+ ObjKind::ObjC));
+ addNSObjectMethSummary(GetNullarySelector("dealloc", Ctx), Summ);
+
+ // Create the "autorelease" selector.
+ Summ = getPersistentSummary(NoRet, ScratchArgs, ArgEffect(Autorelease,
+ ObjKind::ObjC));
+ addNSObjectMethSummary(GetNullarySelector("autorelease", Ctx), Summ);
+
+ // For NSWindow, allocated objects are (initially) self-owned.
+ // FIXME: For now we opt for false negatives with NSWindow, as these objects
+ // self-own themselves. However, they only do this once they are displayed.
+ // Thus, we need to track an NSWindow's display status.
+ // This is tracked in <rdar://problem/6062711>.
+ // See also http://llvm.org/bugs/show_bug.cgi?id=3714.
+ const RetainSummary *NoTrackYet =
+ getPersistentSummary(RetEffect::MakeNoRet(), ScratchArgs,
+ ArgEffect(StopTracking), ArgEffect(StopTracking));
+
+ addClassMethSummary("NSWindow", "alloc", NoTrackYet);
+
+ // For NSPanel (which subclasses NSWindow), allocated objects are not
+ // self-owned.
+ // FIXME: For now we don't track NSPanels. object for the same reason
+ // as for NSWindow objects.
+ addClassMethSummary("NSPanel", "alloc", NoTrackYet);
+
+ // For NSNull, objects returned by +null are singletons that ignore
+ // retain/release semantics. Just don't track them.
+ // <rdar://problem/12858915>
+ addClassMethSummary("NSNull", "null", NoTrackYet);
+
+ // Don't track allocated autorelease pools, as it is okay to prematurely
+ // exit a method.
+ addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet);
+ addClassMethSummary("NSAutoreleasePool", "allocWithZone", NoTrackYet, false);
+ addClassMethSummary("NSAutoreleasePool", "new", NoTrackYet);
+
+ // Create summaries QCRenderer/QCView -createSnapShotImageOfType:
+ addInstMethSummary("QCRenderer", AllocSumm, "createSnapshotImageOfType");
+ addInstMethSummary("QCView", AllocSumm, "createSnapshotImageOfType");
+
+ // Create summaries for CIContext, 'createCGImage' and
+ // 'createCGLayerWithSize'. These objects are CF objects, and are not
+ // automatically garbage collected.
+ addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect");
+ addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect",
+ "format", "colorSpace");
+ addInstMethSummary("CIContext", CFAllocSumm, "createCGLayerWithSize", "info");
+}
+
+CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) {
+ ASTContext &Ctx = MD->getASTContext();
+ LangOptions L = Ctx.getLangOpts();
+ RetainSummaryManager M(Ctx, L.ObjCAutoRefCount,
+ /*TrackNSAndCFObjects=*/true,
+ /*TrackOSObjects=*/false);
+ const RetainSummary *S = M.getMethodSummary(MD);
+ CallEffects CE(S->getRetEffect(), S->getReceiverEffect());
+ unsigned N = MD->param_size();
+ for (unsigned i = 0; i < N; ++i) {
+ CE.Args.push_back(S->getArg(i));
+ }
+ return CE;
+}
+
+CallEffects CallEffects::getEffect(const FunctionDecl *FD) {
+ ASTContext &Ctx = FD->getASTContext();
+ LangOptions L = Ctx.getLangOpts();
+ RetainSummaryManager M(Ctx, L.ObjCAutoRefCount,
+ /*TrackNSAndCFObjects=*/true,
+ /*TrackOSObjects=*/false);
+ const RetainSummary *S = M.getFunctionSummary(FD);
+ CallEffects CE(S->getRetEffect());
+ unsigned N = FD->param_size();
+ for (unsigned i = 0; i < N; ++i) {
+ CE.Args.push_back(S->getArg(i));
+ }
+ return CE;
+}
diff --git a/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp b/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp
deleted file mode 100644
index d379562bf325..000000000000
--- a/lib/StaticAnalyzer/Core/SMTConstraintManager.cpp
+++ /dev/null
@@ -1,181 +0,0 @@
-//== SMTConstraintManager.cpp -----------------------------------*- C++ -*--==//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h"
-#include "clang/Basic/TargetInfo.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
-
-using namespace clang;
-using namespace ento;
-
-ProgramStateRef SMTConstraintManager::assumeSym(ProgramStateRef State,
- SymbolRef Sym,
- bool Assumption) {
- ASTContext &Ctx = getBasicVals().getContext();
-
- QualType RetTy;
- bool hasComparison;
-
- SMTExprRef Exp = Solver->getExpr(Ctx, Sym, &RetTy, &hasComparison);
-
- // Create zero comparison for implicit boolean cast, with reversed assumption
- if (!hasComparison && !RetTy->isBooleanType())
- return assumeExpr(State, Sym,
- Solver->getZeroExpr(Ctx, Exp, RetTy, !Assumption));
-
- return assumeExpr(State, Sym, Assumption ? Exp : Solver->mkNot(Exp));
-}
-
-ProgramStateRef SMTConstraintManager::assumeSymInclusiveRange(
- ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From,
- const llvm::APSInt &To, bool InRange) {
- ASTContext &Ctx = getBasicVals().getContext();
- return assumeExpr(State, Sym,
- Solver->getRangeExpr(Ctx, Sym, From, To, InRange));
-}
-
-ProgramStateRef
-SMTConstraintManager::assumeSymUnsupported(ProgramStateRef State, SymbolRef Sym,
- bool Assumption) {
- // Skip anything that is unsupported
- return State;
-}
-
-ConditionTruthVal SMTConstraintManager::checkNull(ProgramStateRef State,
- SymbolRef Sym) {
- ASTContext &Ctx = getBasicVals().getContext();
-
- QualType RetTy;
- // The expression may be casted, so we cannot call getZ3DataExpr() directly
- SMTExprRef VarExp = Solver->getExpr(Ctx, Sym, &RetTy);
- SMTExprRef Exp = Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/true);
-
- // Negate the constraint
- SMTExprRef NotExp =
- Solver->getZeroExpr(Ctx, VarExp, RetTy, /*Assumption=*/false);
-
- Solver->reset();
- addStateConstraints(State);
-
- Solver->push();
- Solver->addConstraint(Exp);
- ConditionTruthVal isSat = Solver->check();
-
- Solver->pop();
- Solver->addConstraint(NotExp);
- ConditionTruthVal isNotSat = Solver->check();
-
- // Zero is the only possible solution
- if (isSat.isConstrainedTrue() && isNotSat.isConstrainedFalse())
- return true;
-
- // Zero is not a solution
- if (isSat.isConstrainedFalse() && isNotSat.isConstrainedTrue())
- return false;
-
- // Zero may be a solution
- return ConditionTruthVal();
-}
-
-const llvm::APSInt *SMTConstraintManager::getSymVal(ProgramStateRef State,
- SymbolRef Sym) const {
- BasicValueFactory &BVF = getBasicVals();
- ASTContext &Ctx = BVF.getContext();
-
- if (const SymbolData *SD = dyn_cast<SymbolData>(Sym)) {
- QualType Ty = Sym->getType();
- assert(!Ty->isRealFloatingType());
- llvm::APSInt Value(Ctx.getTypeSize(Ty),
- !Ty->isSignedIntegerOrEnumerationType());
-
- SMTExprRef Exp =
- Solver->fromData(SD->getSymbolID(), Ty, Ctx.getTypeSize(Ty));
-
- Solver->reset();
- addStateConstraints(State);
-
- // Constraints are unsatisfiable
- ConditionTruthVal isSat = Solver->check();
- if (!isSat.isConstrainedTrue())
- return nullptr;
-
- // Model does not assign interpretation
- if (!Solver->getInterpretation(Exp, Value))
- return nullptr;
-
- // A value has been obtained, check if it is the only value
- SMTExprRef NotExp = Solver->fromBinOp(
- Exp, BO_NE,
- Ty->isBooleanType() ? Solver->fromBoolean(Value.getBoolValue())
- : Solver->fromAPSInt(Value),
- false);
-
- Solver->addConstraint(NotExp);
-
- ConditionTruthVal isNotSat = Solver->check();
- if (isNotSat.isConstrainedTrue())
- return nullptr;
-
- // This is the only solution, store it
- return &BVF.getValue(Value);
- }
-
- if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) {
- SymbolRef CastSym = SC->getOperand();
- QualType CastTy = SC->getType();
- // Skip the void type
- if (CastTy->isVoidType())
- return nullptr;
-
- const llvm::APSInt *Value;
- if (!(Value = getSymVal(State, CastSym)))
- return nullptr;
- return &BVF.Convert(SC->getType(), *Value);
- }
-
- if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) {
- const llvm::APSInt *LHS, *RHS;
- if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE)) {
- LHS = getSymVal(State, SIE->getLHS());
- RHS = &SIE->getRHS();
- } else if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE)) {
- LHS = &ISE->getLHS();
- RHS = getSymVal(State, ISE->getRHS());
- } else if (const SymSymExpr *SSM = dyn_cast<SymSymExpr>(BSE)) {
- // Early termination to avoid expensive call
- LHS = getSymVal(State, SSM->getLHS());
- RHS = LHS ? getSymVal(State, SSM->getRHS()) : nullptr;
- } else {
- llvm_unreachable("Unsupported binary expression to get symbol value!");
- }
-
- if (!LHS || !RHS)
- return nullptr;
-
- llvm::APSInt ConvertedLHS, ConvertedRHS;
- QualType LTy, RTy;
- std::tie(ConvertedLHS, LTy) = Solver->fixAPSInt(Ctx, *LHS);
- std::tie(ConvertedRHS, RTy) = Solver->fixAPSInt(Ctx, *RHS);
- Solver->doIntTypeConversion<llvm::APSInt, &SMTSolver::castAPSInt>(
- Ctx, ConvertedLHS, LTy, ConvertedRHS, RTy);
- return BVF.evalAPSInt(BSE->getOpcode(), ConvertedLHS, ConvertedRHS);
- }
-
- llvm_unreachable("Unsupported expression to get symbol value!");
-}
-
-ConditionTruthVal
-SMTConstraintManager::checkModel(ProgramStateRef State,
- const SMTExprRef &Exp) const {
- Solver->reset();
- Solver->addConstraint(Exp);
- addStateConstraints(State);
- return Solver->check();
-}
diff --git a/lib/StaticAnalyzer/Core/SValBuilder.cpp b/lib/StaticAnalyzer/Core/SValBuilder.cpp
index f292dca8e99f..6c0d487c8a87 100644
--- a/lib/StaticAnalyzer/Core/SValBuilder.cpp
+++ b/lib/StaticAnalyzer/Core/SValBuilder.cpp
@@ -271,8 +271,8 @@ DefinedSVal SValBuilder::getBlockPointer(const BlockDecl *block,
/// Return a memory region for the 'this' object reference.
loc::MemRegionVal SValBuilder::getCXXThis(const CXXMethodDecl *D,
const StackFrameContext *SFC) {
- return loc::MemRegionVal(getRegionManager().
- getCXXThisRegion(D->getThisType(getContext()), SFC));
+ return loc::MemRegionVal(
+ getRegionManager().getCXXThisRegion(D->getThisType(), SFC));
}
/// Return a memory region for the 'this' object reference.
@@ -362,9 +362,9 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
return None;
ASTContext &Ctx = getContext();
- llvm::APSInt Result;
+ Expr::EvalResult Result;
if (E->EvaluateAsInt(Result, Ctx))
- return makeIntVal(Result);
+ return makeIntVal(Result.Val.getInt());
if (Loc::isLocType(E->getType()))
if (E->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNotNull))
@@ -375,8 +375,7 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) {
}
}
-SVal SValBuilder::makeSymExprValNN(ProgramStateRef State,
- BinaryOperator::Opcode Op,
+SVal SValBuilder::makeSymExprValNN(BinaryOperator::Opcode Op,
NonLoc LHS, NonLoc RHS,
QualType ResultTy) {
const SymExpr *symLHS = LHS.getAsSymExpr();
@@ -385,8 +384,8 @@ SVal SValBuilder::makeSymExprValNN(ProgramStateRef State,
// TODO: When the Max Complexity is reached, we should conjure a symbol
// instead of generating an Unknown value and propagate the taint info to it.
const unsigned MaxComp = StateMgr.getOwningEngine()
- ->getAnalysisManager()
- .options.getMaxSymbolComplexity();
+ .getAnalysisManager()
+ .options.MaxSymbolComplexity;
if (symLHS && symRHS &&
(symLHS->computeComplexity() + symRHS->computeComplexity()) < MaxComp)
diff --git a/lib/StaticAnalyzer/Core/SVals.cpp b/lib/StaticAnalyzer/Core/SVals.cpp
index 559ca2c9840d..933c5c330072 100644
--- a/lib/StaticAnalyzer/Core/SVals.cpp
+++ b/lib/StaticAnalyzer/Core/SVals.cpp
@@ -85,7 +85,7 @@ const FunctionDecl *SVal::getAsFunctionDecl() const {
SymbolRef SVal::getAsLocSymbol(bool IncludeBaseRegions) const {
// FIXME: should we consider SymbolRef wrapped in CodeTextRegion?
if (Optional<nonloc::LocAsInteger> X = getAs<nonloc::LocAsInteger>())
- return X->getLoc().getAsLocSymbol();
+ return X->getLoc().getAsLocSymbol(IncludeBaseRegions);
if (Optional<loc::MemRegionVal> X = getAs<loc::MemRegionVal>()) {
const MemRegion *R = X->getRegion();
@@ -171,6 +171,10 @@ const TypedValueRegion *nonloc::LazyCompoundVal::getRegion() const {
return static_cast<const LazyCompoundValData*>(Data)->getRegion();
}
+bool nonloc::PointerToMember::isNullMemberPointer() const {
+ return getPTMData().isNull();
+}
+
const DeclaratorDecl *nonloc::PointerToMember::getDecl() const {
const auto PTMD = this->getPTMData();
if (PTMD.isNull())
diff --git a/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp
new file mode 100644
index 000000000000..fecbc0001079
--- /dev/null
+++ b/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp
@@ -0,0 +1,349 @@
+//===--- SarifDiagnostics.cpp - Sarif Diagnostics for Paths -----*- 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 SarifDiagnostics object.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Basic/Version.h"
+#include "clang/Lex/Preprocessor.h"
+#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h"
+#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/Support/JSON.h"
+#include "llvm/Support/Path.h"
+
+using namespace llvm;
+using namespace clang;
+using namespace ento;
+
+namespace {
+class SarifDiagnostics : public PathDiagnosticConsumer {
+ std::string OutputFile;
+
+public:
+ SarifDiagnostics(AnalyzerOptions &, const std::string &Output)
+ : OutputFile(Output) {}
+ ~SarifDiagnostics() override = default;
+
+ void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
+ FilesMade *FM) override;
+
+ StringRef getName() const override { return "SarifDiagnostics"; }
+ PathGenerationScheme getGenerationScheme() const override { return Minimal; }
+ bool supportsLogicalOpControlFlow() const override { return true; }
+ bool supportsCrossFileDiagnostics() const override { return true; }
+};
+} // end anonymous namespace
+
+void ento::createSarifDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts,
+ PathDiagnosticConsumers &C,
+ const std::string &Output,
+ const Preprocessor &) {
+ C.push_back(new SarifDiagnostics(AnalyzerOpts, Output));
+}
+
+static StringRef getFileName(const FileEntry &FE) {
+ StringRef Filename = FE.tryGetRealPathName();
+ if (Filename.empty())
+ Filename = FE.getName();
+ return Filename;
+}
+
+static std::string percentEncodeURICharacter(char C) {
+ // RFC 3986 claims alpha, numeric, and this handful of
+ // characters are not reserved for the path component and
+ // should be written out directly. Otherwise, percent
+ // encode the character and write that out instead of the
+ // reserved character.
+ if (llvm::isAlnum(C) ||
+ StringRef::npos != StringRef("-._~:@!$&'()*+,;=").find(C))
+ return std::string(&C, 1);
+ return "%" + llvm::toHex(StringRef(&C, 1));
+}
+
+static std::string fileNameToURI(StringRef Filename) {
+ llvm::SmallString<32> Ret = StringRef("file://");
+
+ // Get the root name to see if it has a URI authority.
+ StringRef Root = sys::path::root_name(Filename);
+ if (Root.startswith("//")) {
+ // There is an authority, so add it to the URI.
+ Ret += Root.drop_front(2).str();
+ } else if (!Root.empty()) {
+ // There is no authority, so end the component and add the root to the URI.
+ Ret += Twine("/" + Root).str();
+ }
+
+ auto Iter = sys::path::begin(Filename), End = sys::path::end(Filename);
+ assert(Iter != End && "Expected there to be a non-root path component.");
+ // Add the rest of the path components, encoding any reserved characters;
+ // we skip past the first path component, as it was handled it above.
+ std::for_each(++Iter, End, [&Ret](StringRef Component) {
+ // For reasons unknown to me, we may get a backslash with Windows native
+ // paths for the initial backslash following the drive component, which
+ // we need to ignore as a URI path part.
+ if (Component == "\\")
+ return;
+
+ // Add the separator between the previous path part and the one being
+ // currently processed.
+ Ret += "/";
+
+ // URI encode the part.
+ for (char C : Component) {
+ Ret += percentEncodeURICharacter(C);
+ }
+ });
+
+ return Ret.str().str();
+}
+
+static json::Object createFileLocation(const FileEntry &FE) {
+ return json::Object{{"uri", fileNameToURI(getFileName(FE))}};
+}
+
+static json::Object createFile(const FileEntry &FE) {
+ return json::Object{{"fileLocation", createFileLocation(FE)},
+ {"roles", json::Array{"resultFile"}},
+ {"length", FE.getSize()},
+ {"mimeType", "text/plain"}};
+}
+
+static json::Object createFileLocation(const FileEntry &FE,
+ json::Array &Files) {
+ std::string FileURI = fileNameToURI(getFileName(FE));
+
+ // See if the Files array contains this URI already. If it does not, create
+ // a new file object to add to the array.
+ auto I = llvm::find_if(Files, [&](const json::Value &File) {
+ if (const json::Object *Obj = File.getAsObject()) {
+ if (const json::Object *FileLoc = Obj->getObject("fileLocation")) {
+ Optional<StringRef> URI = FileLoc->getString("uri");
+ return URI && URI->equals(FileURI);
+ }
+ }
+ return false;
+ });
+
+ // Calculate the index within the file location array so it can be stored in
+ // the JSON object.
+ auto Index = static_cast<unsigned>(std::distance(Files.begin(), I));
+ if (I == Files.end())
+ Files.push_back(createFile(FE));
+
+ return json::Object{{"uri", FileURI}, {"fileIndex", Index}};
+}
+
+static json::Object createTextRegion(SourceRange R, const SourceManager &SM) {
+ return json::Object{
+ {"startLine", SM.getExpansionLineNumber(R.getBegin())},
+ {"endLine", SM.getExpansionLineNumber(R.getEnd())},
+ {"startColumn", SM.getExpansionColumnNumber(R.getBegin())},
+ {"endColumn", SM.getExpansionColumnNumber(R.getEnd())}};
+}
+
+static json::Object createPhysicalLocation(SourceRange R, const FileEntry &FE,
+ const SourceManager &SMgr,
+ json::Array &Files) {
+ return json::Object{{{"fileLocation", createFileLocation(FE, Files)},
+ {"region", createTextRegion(R, SMgr)}}};
+}
+
+enum class Importance { Important, Essential, Unimportant };
+
+static StringRef importanceToStr(Importance I) {
+ switch (I) {
+ case Importance::Important:
+ return "important";
+ case Importance::Essential:
+ return "essential";
+ case Importance::Unimportant:
+ return "unimportant";
+ }
+ llvm_unreachable("Fully covered switch is not so fully covered");
+}
+
+static json::Object createThreadFlowLocation(json::Object &&Location,
+ Importance I) {
+ return json::Object{{"location", std::move(Location)},
+ {"importance", importanceToStr(I)}};
+}
+
+static json::Object createMessage(StringRef Text) {
+ return json::Object{{"text", Text.str()}};
+}
+
+static json::Object createLocation(json::Object &&PhysicalLocation,
+ StringRef Message = "") {
+ json::Object Ret{{"physicalLocation", std::move(PhysicalLocation)}};
+ if (!Message.empty())
+ Ret.insert({"message", createMessage(Message)});
+ return Ret;
+}
+
+static Importance calculateImportance(const PathDiagnosticPiece &Piece) {
+ switch (Piece.getKind()) {
+ case PathDiagnosticPiece::Kind::Call:
+ case PathDiagnosticPiece::Kind::Macro:
+ case PathDiagnosticPiece::Kind::Note:
+ // FIXME: What should be reported here?
+ break;
+ case PathDiagnosticPiece::Kind::Event:
+ return Piece.getTagStr() == "ConditionBRVisitor" ? Importance::Important
+ : Importance::Essential;
+ case PathDiagnosticPiece::Kind::ControlFlow:
+ return Importance::Unimportant;
+ }
+ return Importance::Unimportant;
+}
+
+static json::Object createThreadFlow(const PathPieces &Pieces,
+ json::Array &Files) {
+ const SourceManager &SMgr = Pieces.front()->getLocation().getManager();
+ json::Array Locations;
+ for (const auto &Piece : Pieces) {
+ const PathDiagnosticLocation &P = Piece->getLocation();
+ Locations.push_back(createThreadFlowLocation(
+ createLocation(createPhysicalLocation(P.asRange(),
+ *P.asLocation().getFileEntry(),
+ SMgr, Files),
+ Piece->getString()),
+ calculateImportance(*Piece)));
+ }
+ return json::Object{{"locations", std::move(Locations)}};
+}
+
+static json::Object createCodeFlow(const PathPieces &Pieces,
+ json::Array &Files) {
+ return json::Object{
+ {"threadFlows", json::Array{createThreadFlow(Pieces, Files)}}};
+}
+
+static json::Object createTool() {
+ return json::Object{{"name", "clang"},
+ {"fullName", "clang static analyzer"},
+ {"language", "en-US"},
+ {"version", getClangFullVersion()}};
+}
+
+static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files,
+ const StringMap<unsigned> &RuleMapping) {
+ const PathPieces &Path = Diag.path.flatten(false);
+ const SourceManager &SMgr = Path.front()->getLocation().getManager();
+
+ auto Iter = RuleMapping.find(Diag.getCheckName());
+ assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?");
+
+ return json::Object{
+ {"message", createMessage(Diag.getVerboseDescription())},
+ {"codeFlows", json::Array{createCodeFlow(Path, Files)}},
+ {"locations",
+ json::Array{createLocation(createPhysicalLocation(
+ Diag.getLocation().asRange(),
+ *Diag.getLocation().asLocation().getFileEntry(), SMgr, Files))}},
+ {"ruleIndex", Iter->getValue()},
+ {"ruleId", Diag.getCheckName()}};
+}
+
+static StringRef getRuleDescription(StringRef CheckName) {
+ return llvm::StringSwitch<StringRef>(CheckName)
+#define GET_CHECKERS
+#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \
+ .Case(FULLNAME, HELPTEXT)
+#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
+#undef CHECKER
+#undef GET_CHECKERS
+ ;
+}
+
+static StringRef getRuleHelpURIStr(StringRef CheckName) {
+ return llvm::StringSwitch<StringRef>(CheckName)
+#define GET_CHECKERS
+#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \
+ .Case(FULLNAME, DOC_URI)
+#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
+#undef CHECKER
+#undef GET_CHECKERS
+ ;
+}
+
+static json::Object createRule(const PathDiagnostic &Diag) {
+ StringRef CheckName = Diag.getCheckName();
+ json::Object Ret{
+ {"fullDescription", createMessage(getRuleDescription(CheckName))},
+ {"name", createMessage(CheckName)},
+ {"id", CheckName}};
+
+ std::string RuleURI = getRuleHelpURIStr(CheckName);
+ if (!RuleURI.empty())
+ Ret["helpUri"] = RuleURI;
+
+ return Ret;
+}
+
+static json::Array createRules(std::vector<const PathDiagnostic *> &Diags,
+ StringMap<unsigned> &RuleMapping) {
+ json::Array Rules;
+ llvm::StringSet<> Seen;
+
+ llvm::for_each(Diags, [&](const PathDiagnostic *D) {
+ StringRef RuleID = D->getCheckName();
+ std::pair<llvm::StringSet<>::iterator, bool> P = Seen.insert(RuleID);
+ if (P.second) {
+ RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index.
+ Rules.push_back(createRule(*D));
+ }
+ });
+
+ return Rules;
+}
+
+static json::Object createResources(std::vector<const PathDiagnostic *> &Diags,
+ StringMap<unsigned> &RuleMapping) {
+ return json::Object{{"rules", createRules(Diags, RuleMapping)}};
+}
+
+static json::Object createRun(std::vector<const PathDiagnostic *> &Diags) {
+ json::Array Results, Files;
+ StringMap<unsigned> RuleMapping;
+ json::Object Resources = createResources(Diags, RuleMapping);
+
+ llvm::for_each(Diags, [&](const PathDiagnostic *D) {
+ Results.push_back(createResult(*D, Files, RuleMapping));
+ });
+
+ return json::Object{{"tool", createTool()},
+ {"resources", std::move(Resources)},
+ {"results", std::move(Results)},
+ {"files", std::move(Files)}};
+}
+
+void SarifDiagnostics::FlushDiagnosticsImpl(
+ std::vector<const PathDiagnostic *> &Diags, FilesMade *) {
+ // We currently overwrite the file if it already exists. However, it may be
+ // useful to add a feature someday that allows the user to append a run to an
+ // existing SARIF file. One danger from that approach is that the size of the
+ // file can become large very quickly, so decoding into JSON to append a run
+ // may be an expensive operation.
+ std::error_code EC;
+ llvm::raw_fd_ostream OS(OutputFile, EC, llvm::sys::fs::F_Text);
+ if (EC) {
+ llvm::errs() << "warning: could not create file: " << EC.message() << '\n';
+ return;
+ }
+ json::Object Sarif{
+ {"$schema",
+ "http://json.schemastore.org/sarif-2.0.0-csd.2.beta.2018-11-28"},
+ {"version", "2.0.0-csd.2.beta.2018-11-28"},
+ {"runs", json::Array{createRun(Diags)}}};
+ OS << llvm::formatv("{0:2}", json::Value(std::move(Sarif)));
+}
diff --git a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
index 62c54fc956a9..fc57cecac9cb 100644
--- a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
+++ b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp
@@ -454,12 +454,12 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State,
QualType SingleTy;
auto &Opts =
- StateMgr.getOwningEngine()->getAnalysisManager().getAnalyzerOptions();
+ StateMgr.getOwningEngine().getAnalysisManager().getAnalyzerOptions();
// FIXME: After putting complexity threshold to the symbols we can always
// rearrange additive operations but rearrange comparisons only if
// option is set.
- if(!Opts.shouldAggressivelySimplifyBinaryOperation())
+ if(!Opts.ShouldAggressivelySimplifyBinaryOperation)
return None;
SymbolRef LSym = Lhs.getAsSymbol();
@@ -475,9 +475,6 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State,
SingleTy = ResultTy;
if (LSym->getType() != SingleTy)
return None;
- // Substracting unsigned integers is a nightmare.
- if (!SingleTy->isSignedIntegerOrEnumerationType())
- return None;
} else {
// Don't rearrange other operations.
return None;
@@ -485,6 +482,10 @@ static Optional<NonLoc> tryRearrange(ProgramStateRef State,
assert(!SingleTy.isNull() && "We should have figured out the type by now!");
+ // Rearrange signed symbolic expressions only
+ if (!SingleTy->isSignedIntegerOrEnumerationType())
+ return None;
+
SymbolRef RSym = Rhs.getAsSymbol();
if (!RSym || RSym->getType() != SingleTy)
return None;
@@ -534,7 +535,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
while (1) {
switch (lhs.getSubKind()) {
default:
- return makeSymExprValNN(state, op, lhs, rhs, resultTy);
+ return makeSymExprValNN(op, lhs, rhs, resultTy);
case nonloc::PointerToMemberKind: {
assert(rhs.getSubKind() == nonloc::PointerToMemberKind &&
"Both SVals should have pointer-to-member-type");
@@ -582,7 +583,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
return makeTruthVal(true, resultTy);
default:
// This case also handles pointer arithmetic.
- return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy);
+ return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
}
}
}
@@ -624,7 +625,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
case BO_LE:
case BO_GE:
op = BinaryOperator::reverseComparisonOp(op);
- // FALL-THROUGH
+ LLVM_FALLTHROUGH;
case BO_EQ:
case BO_NE:
case BO_Add:
@@ -638,14 +639,14 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
// (~0)>>a
if (LHSValue.isAllOnesValue() && LHSValue.isSigned())
return evalCastFromNonLoc(lhs, resultTy);
- // FALL-THROUGH
+ LLVM_FALLTHROUGH;
case BO_Shl:
// 0<<a and 0>>a
if (LHSValue == 0)
return evalCastFromNonLoc(lhs, resultTy);
- return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy);
+ return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
default:
- return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy);
+ return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
}
}
case nonloc::SymbolValKind: {
@@ -757,7 +758,7 @@ SVal SimpleSValBuilder::evalBinOpNN(ProgramStateRef state,
return *V;
// Give up -- this is not a symbolic expression we can handle.
- return makeSymExprValNN(state, op, InputLHS, InputRHS, resultTy);
+ return makeSymExprValNN(op, InputLHS, InputRHS, resultTy);
}
}
}
@@ -1201,6 +1202,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state,
const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state,
SVal V) {
+ V = simplifySVal(state, V);
if (V.isUnknownOrUndef())
return nullptr;
diff --git a/lib/StaticAnalyzer/Core/Store.cpp b/lib/StaticAnalyzer/Core/Store.cpp
index 94188a9ef698..4fa937d9658d 100644
--- a/lib/StaticAnalyzer/Core/Store.cpp
+++ b/lib/StaticAnalyzer/Core/Store.cpp
@@ -88,7 +88,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy)
return R;
// We don't know what to make of it. Return a NULL region, which
- // will be interpretted as UnknownVal.
+ // will be interpreted as UnknownVal.
return nullptr;
}
@@ -138,6 +138,7 @@ const MemRegion *StoreManager::castRegion(const MemRegion *R, QualType CastToTy)
case MemRegion::VarRegionKind:
case MemRegion::CXXTempObjectRegionKind:
case MemRegion::CXXBaseObjectRegionKind:
+ case MemRegion::CXXDerivedObjectRegionKind:
return MakeElementRegion(cast<SubRegion>(R), PointeeTy);
case MemRegion::ElementRegionKind: {
@@ -272,9 +273,8 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, const CXXBasePath &Path) {
SVal StoreManager::evalDerivedToBase(SVal Derived, QualType BaseType,
bool IsVirtual) {
- Optional<loc::MemRegionVal> DerivedRegVal =
- Derived.getAs<loc::MemRegionVal>();
- if (!DerivedRegVal)
+ const MemRegion *DerivedReg = Derived.getAsRegion();
+ if (!DerivedReg)
return Derived;
const CXXRecordDecl *BaseDecl = BaseType->getPointeeCXXRecordDecl();
@@ -282,8 +282,18 @@ SVal StoreManager::evalDerivedToBase(SVal Derived, QualType BaseType,
BaseDecl = BaseType->getAsCXXRecordDecl();
assert(BaseDecl && "not a C++ object?");
+ if (const auto *AlreadyDerivedReg =
+ dyn_cast<CXXDerivedObjectRegion>(DerivedReg)) {
+ if (const auto *SR =
+ dyn_cast<SymbolicRegion>(AlreadyDerivedReg->getSuperRegion()))
+ if (SR->getSymbol()->getType()->getPointeeCXXRecordDecl() == BaseDecl)
+ return loc::MemRegionVal(SR);
+
+ DerivedReg = AlreadyDerivedReg->getSuperRegion();
+ }
+
const MemRegion *BaseReg = MRMgr.getCXXBaseObjectRegion(
- BaseDecl, cast<SubRegion>(DerivedRegVal->getRegion()), IsVirtual);
+ BaseDecl, cast<SubRegion>(DerivedReg), IsVirtual);
return loc::MemRegionVal(BaseReg);
}
@@ -365,6 +375,20 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType,
MR = Uncasted;
}
+ // If we're casting a symbolic base pointer to a derived class, use
+ // CXXDerivedObjectRegion to represent the cast. If it's a pointer to an
+ // unrelated type, it must be a weird reinterpret_cast and we have to
+ // be fine with ElementRegion. TODO: Should we instead make
+ // Derived{TargetClass, Element{SourceClass, SR}}?
+ if (const auto *SR = dyn_cast<SymbolicRegion>(MR)) {
+ QualType T = SR->getSymbol()->getType();
+ const CXXRecordDecl *SourceClass = T->getPointeeCXXRecordDecl();
+ if (TargetClass && SourceClass && TargetClass->isDerivedFrom(SourceClass))
+ return loc::MemRegionVal(
+ MRMgr.getCXXDerivedObjectRegion(TargetClass, SR));
+ return loc::MemRegionVal(GetElementZeroRegion(SR, TargetType));
+ }
+
// We failed if the region we ended up with has perfect type info.
Failed = isa<TypedValueRegion>(MR);
return UnknownVal();
@@ -378,6 +402,17 @@ SVal StoreManager::CastRetrievedVal(SVal V, const TypedValueRegion *R,
if (castTy.isNull() || V.isUnknownOrUndef())
return V;
+ // The dispatchCast() call below would convert the int into a float.
+ // What we want, however, is a bit-by-bit reinterpretation of the int
+ // as a float, which usually yields nothing garbage. For now skip casts
+ // from ints to floats.
+ // TODO: What other combinations of types are affected?
+ if (castTy->isFloatingType()) {
+ SymbolRef Sym = V.getAsSymbol();
+ if (Sym && !Sym->getType()->isFloatingType())
+ return UnknownVal();
+ }
+
// When retrieving symbolic pointer and expecting a non-void pointer,
// wrap them into element regions of the expected type if necessary.
// SValBuilder::dispatchCast() doesn't do that, but it is necessary to
diff --git a/lib/StaticAnalyzer/Core/SymbolManager.cpp b/lib/StaticAnalyzer/Core/SymbolManager.cpp
index ed197010ebb7..66273f099a38 100644
--- a/lib/StaticAnalyzer/Core/SymbolManager.cpp
+++ b/lib/StaticAnalyzer/Core/SymbolManager.cpp
@@ -83,7 +83,13 @@ void SymbolCast::dumpToStream(raw_ostream &os) const {
}
void SymbolConjured::dumpToStream(raw_ostream &os) const {
- os << "conj_$" << getSymbolID() << '{' << T.getAsString() << '}';
+ os << "conj_$" << getSymbolID() << '{' << T.getAsString() << ", LC"
+ << LCtx->getID();
+ if (S)
+ os << ", S" << S->getID(LCtx->getDecl()->getASTContext());
+ else
+ os << ", no stmt";
+ os << ", #" << Count << '}';
}
void SymbolDerived::dumpToStream(raw_ostream &os) const {
@@ -395,7 +401,6 @@ void SymbolReaper::markDependentsLive(SymbolRef sym) {
void SymbolReaper::markLive(SymbolRef sym) {
TheLiving[sym] = NotProcessed;
- TheDead.erase(sym);
markDependentsLive(sym);
}
@@ -420,14 +425,6 @@ void SymbolReaper::markInUse(SymbolRef sym) {
MetadataInUse.insert(sym);
}
-bool SymbolReaper::maybeDead(SymbolRef sym) {
- if (isLive(sym))
- return false;
-
- TheDead.insert(sym);
- return true;
-}
-
bool SymbolReaper::isLiveRegion(const MemRegion *MR) {
if (RegionRoots.count(MR))
return true;
diff --git a/lib/StaticAnalyzer/Core/TaintManager.cpp b/lib/StaticAnalyzer/Core/TaintManager.cpp
new file mode 100644
index 000000000000..c34b0ca1839d
--- /dev/null
+++ b/lib/StaticAnalyzer/Core/TaintManager.cpp
@@ -0,0 +1,23 @@
+//== TaintManager.cpp ------------------------------------------ -*- C++ -*--=//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Core/PathSensitive/TaintManager.h"
+
+using namespace clang;
+using namespace ento;
+
+void *ProgramStateTrait<TaintMap>::GDMIndex() {
+ static int index = 0;
+ return &index;
+}
+
+void *ProgramStateTrait<DerivedSymTaint>::GDMIndex() {
+ static int index;
+ return &index;
+}
diff --git a/lib/StaticAnalyzer/Core/WorkList.cpp b/lib/StaticAnalyzer/Core/WorkList.cpp
index 4b227375da9b..e705393cb83a 100644
--- a/lib/StaticAnalyzer/Core/WorkList.cpp
+++ b/lib/StaticAnalyzer/Core/WorkList.cpp
@@ -152,7 +152,7 @@ public:
auto BE = N->getLocation().getAs<BlockEntrance>();
if (!BE) {
- // Assume the choice of the order of the preceeding block entrance was
+ // Assume the choice of the order of the preceding block entrance was
// correct.
StackUnexplored.push_back(U);
} else {
@@ -252,3 +252,63 @@ public:
std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityQueue() {
return llvm::make_unique<UnexploredFirstPriorityQueue>();
}
+
+namespace {
+class UnexploredFirstPriorityLocationQueue : public WorkList {
+ using LocIdentifier = const CFGBlock *;
+
+ // How many times each location was visited.
+ // Is signed because we negate it later in order to have a reversed
+ // comparison.
+ using VisitedTimesMap = llvm::DenseMap<LocIdentifier, int>;
+
+ // Compare by number of times the location was visited first (negated
+ // to prefer less often visited locations), then by insertion time (prefer
+ // expanding nodes inserted sooner first).
+ using QueuePriority = std::pair<int, unsigned long>;
+ using QueueItem = std::pair<WorkListUnit, QueuePriority>;
+
+ struct ExplorationComparator {
+ bool operator() (const QueueItem &LHS, const QueueItem &RHS) {
+ return LHS.second < RHS.second;
+ }
+ };
+
+ // Number of inserted nodes, used to emulate DFS ordering in the priority
+ // queue when insertions are equal.
+ unsigned long Counter = 0;
+
+ // Number of times a current location was reached.
+ VisitedTimesMap NumReached;
+
+ // The top item is the largest one.
+ llvm::PriorityQueue<QueueItem, std::vector<QueueItem>, ExplorationComparator>
+ queue;
+
+public:
+ bool hasWork() const override {
+ return !queue.empty();
+ }
+
+ void enqueue(const WorkListUnit &U) override {
+ const ExplodedNode *N = U.getNode();
+ unsigned NumVisited = 0;
+ if (auto BE = N->getLocation().getAs<BlockEntrance>())
+ NumVisited = NumReached[BE->getBlock()]++;
+
+ queue.push(std::make_pair(U, std::make_pair(-NumVisited, ++Counter)));
+ }
+
+ WorkListUnit dequeue() override {
+ QueueItem U = queue.top();
+ queue.pop();
+ return U.first;
+ }
+
+};
+
+}
+
+std::unique_ptr<WorkList> WorkList::makeUnexploredFirstPriorityLocationQueue() {
+ return llvm::make_unique<UnexploredFirstPriorityLocationQueue>();
+}
diff --git a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp
index 7379ded49c80..c4729f969f33 100644
--- a/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp
+++ b/lib/StaticAnalyzer/Core/Z3ConstraintManager.cpp
@@ -11,10 +11,7 @@
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConstraintManager.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTContext.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTExpr.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSolver.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/SMTSort.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/SMTConv.h"
#include "clang/Config/config.h"
@@ -49,15 +46,15 @@ public:
// Function used to report errors
void Z3ErrorHandler(Z3_context Context, Z3_error_code Error) {
llvm::report_fatal_error("Z3 error: " +
- llvm::Twine(Z3_get_error_msg_ex(Context, Error)));
+ llvm::Twine(Z3_get_error_msg(Context, Error)));
}
/// Wrapper for Z3 context
-class Z3Context : public SMTContext {
+class Z3Context {
public:
Z3_context Context;
- Z3Context() : SMTContext() {
+ Z3Context() {
Context = Z3_mk_context_rc(Z3Config().Config);
// The error function is set here because the context is the first object
// created by the backend
@@ -80,32 +77,27 @@ class Z3Sort : public SMTSort {
public:
/// Default constructor, mainly used by make_shared
- Z3Sort(Z3Context &C, Z3_sort ZS) : SMTSort(), Context(C), Sort(ZS) {
+ Z3Sort(Z3Context &C, Z3_sort ZS) : Context(C), Sort(ZS) {
Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
}
/// Override implicit copy constructor for correct reference counting.
- Z3Sort(const Z3Sort &Copy)
- : SMTSort(), Context(Copy.Context), Sort(Copy.Sort) {
+ Z3Sort(const Z3Sort &Other) : Context(Other.Context), Sort(Other.Sort) {
Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
}
- /// Provide move constructor
- Z3Sort(Z3Sort &&Move) : SMTSort(), Context(Move.Context), Sort(nullptr) {
- *this = std::move(Move);
- }
-
- /// Provide move assignment constructor
- Z3Sort &operator=(Z3Sort &&Move) {
- if (this != &Move) {
- if (Sort)
- Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
- Sort = Move.Sort;
- Move.Sort = nullptr;
- }
+ /// Override implicit copy assignment constructor for correct reference
+ /// counting.
+ Z3Sort &operator=(const Z3Sort &Other) {
+ Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Other.Sort));
+ Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
+ Sort = Other.Sort;
return *this;
}
+ Z3Sort(Z3Sort &&Other) = delete;
+ Z3Sort &operator=(Z3Sort &&Other) = delete;
+
~Z3Sort() {
if (Sort)
Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
@@ -137,13 +129,6 @@ public:
static_cast<const Z3Sort &>(Other).Sort);
}
- Z3Sort &operator=(const Z3Sort &Move) {
- Z3_inc_ref(Context.Context, reinterpret_cast<Z3_ast>(Move.Sort));
- Z3_dec_ref(Context.Context, reinterpret_cast<Z3_ast>(Sort));
- Sort = Move.Sort;
- return *this;
- }
-
void print(raw_ostream &OS) const override {
OS << Z3_sort_to_string(Context.Context, Sort);
}
@@ -170,22 +155,18 @@ public:
Z3_inc_ref(Context.Context, AST);
}
- /// Provide move constructor
- Z3Expr(Z3Expr &&Move) : SMTExpr(), Context(Move.Context), AST(nullptr) {
- *this = std::move(Move);
- }
-
- /// Provide move assignment constructor
- Z3Expr &operator=(Z3Expr &&Move) {
- if (this != &Move) {
- if (AST)
- Z3_dec_ref(Context.Context, AST);
- AST = Move.AST;
- Move.AST = nullptr;
- }
+ /// Override implicit copy assignment constructor for correct reference
+ /// counting.
+ Z3Expr &operator=(const Z3Expr &Other) {
+ Z3_inc_ref(Context.Context, Other.AST);
+ Z3_dec_ref(Context.Context, AST);
+ AST = Other.AST;
return *this;
}
+ Z3Expr(Z3Expr &&Other) = delete;
+ Z3Expr &operator=(Z3Expr &&Other) = delete;
+
~Z3Expr() {
if (AST)
Z3_dec_ref(Context.Context, AST);
@@ -205,14 +186,6 @@ public:
static_cast<const Z3Expr &>(Other).AST);
}
- /// Override implicit move constructor for correct reference counting.
- Z3Expr &operator=(const Z3Expr &Move) {
- Z3_inc_ref(Context.Context, Move.AST);
- Z3_dec_ref(Context.Context, AST);
- AST = Move.AST;
- return *this;
- }
-
void print(raw_ostream &OS) const override {
OS << Z3_ast_to_string(Context.Context, AST);
}
@@ -231,30 +204,13 @@ class Z3Model {
public:
Z3Model(Z3Context &C, Z3_model ZM) : Context(C), Model(ZM) {
- assert(C.Context != nullptr);
Z3_model_inc_ref(Context.Context, Model);
}
- /// Override implicit copy constructor for correct reference counting.
- Z3Model(const Z3Model &Copy) : Context(Copy.Context), Model(Copy.Model) {
- Z3_model_inc_ref(Context.Context, Model);
- }
-
- /// Provide move constructor
- Z3Model(Z3Model &&Move) : Context(Move.Context), Model(nullptr) {
- *this = std::move(Move);
- }
-
- /// Provide move assignment constructor
- Z3Model &operator=(Z3Model &&Move) {
- if (this != &Move) {
- if (Model)
- Z3_model_dec_ref(Context.Context, Model);
- Model = Move.Model;
- Move.Model = nullptr;
- }
- return *this;
- }
+ Z3Model(const Z3Model &Other) = delete;
+ Z3Model(Z3Model &&Other) = delete;
+ Z3Model &operator=(Z3Model &Other) = delete;
+ Z3Model &operator=(Z3Model &&Other) = delete;
~Z3Model() {
if (Model)
@@ -313,32 +269,14 @@ class Z3Solver : public SMTSolver {
Z3_solver Solver;
public:
- Z3Solver() : SMTSolver(), Solver(Z3_mk_simple_solver(Context.Context)) {
- Z3_solver_inc_ref(Context.Context, Solver);
- }
-
- /// Override implicit copy constructor for correct reference counting.
- Z3Solver(const Z3Solver &Copy)
- : SMTSolver(), Context(Copy.Context), Solver(Copy.Solver) {
+ Z3Solver() : Solver(Z3_mk_simple_solver(Context.Context)) {
Z3_solver_inc_ref(Context.Context, Solver);
}
- /// Provide move constructor
- Z3Solver(Z3Solver &&Move)
- : SMTSolver(), Context(Move.Context), Solver(nullptr) {
- *this = std::move(Move);
- }
-
- /// Provide move assignment constructor
- Z3Solver &operator=(Z3Solver &&Move) {
- if (this != &Move) {
- if (Solver)
- Z3_solver_dec_ref(Context.Context, Solver);
- Solver = Move.Solver;
- Move.Solver = nullptr;
- }
- return *this;
- }
+ Z3Solver(const Z3Solver &Other) = delete;
+ Z3Solver(Z3Solver &&Other) = delete;
+ Z3Solver &operator=(Z3Solver &Other) = delete;
+ Z3Solver &operator=(Z3Solver &&Other) = delete;
~Z3Solver() {
if (Solver)
@@ -674,7 +612,7 @@ public:
toZ3Expr(*From).AST, toZ3Sort(*To).Sort)));
}
- SMTExprRef mkFPtoSBV(const SMTExprRef &From, const SMTSortRef &To) override {
+ SMTExprRef mkSBVtoFP(const SMTExprRef &From, const SMTSortRef &To) override {
SMTExprRef RoundingMode = getFloatRoundingMode();
return newExprRef(Z3Expr(
Context,
@@ -682,7 +620,7 @@ public:
toZ3Expr(*From).AST, toZ3Sort(*To).Sort)));
}
- SMTExprRef mkFPtoUBV(const SMTExprRef &From, const SMTSortRef &To) override {
+ SMTExprRef mkUBVtoFP(const SMTExprRef &From, const SMTSortRef &To) override {
SMTExprRef RoundingMode = getFloatRoundingMode();
return newExprRef(Z3Expr(
Context,
@@ -690,14 +628,14 @@ public:
toZ3Expr(*From).AST, toZ3Sort(*To).Sort)));
}
- SMTExprRef mkSBVtoFP(const SMTExprRef &From, unsigned ToWidth) override {
+ SMTExprRef mkFPtoSBV(const SMTExprRef &From, unsigned ToWidth) override {
SMTExprRef RoundingMode = getFloatRoundingMode();
return newExprRef(Z3Expr(
Context, Z3_mk_fpa_to_sbv(Context.Context, toZ3Expr(*RoundingMode).AST,
toZ3Expr(*From).AST, ToWidth)));
}
- SMTExprRef mkUBVtoFP(const SMTExprRef &From, unsigned ToWidth) override {
+ SMTExprRef mkFPtoUBV(const SMTExprRef &From, unsigned ToWidth) override {
SMTExprRef RoundingMode = getFloatRoundingMode();
return newExprRef(Z3Expr(
Context, Z3_mk_fpa_to_ubv(Context.Context, toZ3Expr(*RoundingMode).AST,
@@ -736,9 +674,11 @@ public:
llvm::APSInt getBitvector(const SMTExprRef &Exp, unsigned BitWidth,
bool isUnsigned) override {
- return llvm::APSInt(llvm::APInt(
- BitWidth, Z3_get_numeral_string(Context.Context, toZ3Expr(*Exp).AST),
- 10));
+ return llvm::APSInt(
+ llvm::APInt(BitWidth,
+ Z3_get_numeral_string(Context.Context, toZ3Expr(*Exp).AST),
+ 10),
+ isUnsigned);
}
bool getBoolean(const SMTExprRef &Exp) override {
@@ -750,42 +690,6 @@ public:
return newExprRef(Z3Expr(Context, Z3_mk_fpa_rne(Context.Context)));
}
- SMTExprRef fromData(const SymbolID ID, const QualType &Ty,
- uint64_t BitWidth) override {
- llvm::Twine Name = "$" + llvm::Twine(ID);
- return mkSymbol(Name.str().c_str(), mkSort(Ty, BitWidth));
- }
-
- SMTExprRef fromBoolean(const bool Bool) override {
- Z3_ast AST =
- Bool ? Z3_mk_true(Context.Context) : Z3_mk_false(Context.Context);
- return newExprRef(Z3Expr(Context, AST));
- }
-
- SMTExprRef fromAPFloat(const llvm::APFloat &Float) override {
- SMTSortRef Sort =
- getFloatSort(llvm::APFloat::semanticsSizeInBits(Float.getSemantics()));
-
- llvm::APSInt Int = llvm::APSInt(Float.bitcastToAPInt(), false);
- SMTExprRef Z3Int = fromAPSInt(Int);
- return newExprRef(Z3Expr(
- Context, Z3_mk_fpa_to_fp_bv(Context.Context, toZ3Expr(*Z3Int).AST,
- toZ3Sort(*Sort).Sort)));
- }
-
- SMTExprRef fromAPSInt(const llvm::APSInt &Int) override {
- SMTSortRef Sort = getBitvectorSort(Int.getBitWidth());
- Z3_ast AST = Z3_mk_numeral(Context.Context, Int.toString(10).c_str(),
- toZ3Sort(*Sort).Sort);
- return newExprRef(Z3Expr(Context, AST));
- }
-
- SMTExprRef fromInt(const char *Int, uint64_t BitWidth) override {
- SMTSortRef Sort = getBitvectorSort(BitWidth);
- Z3_ast AST = Z3_mk_numeral(Context.Context, Int, toZ3Sort(*Sort).Sort);
- return newExprRef(Z3Expr(Context, AST));
- }
-
bool toAPFloat(const SMTSortRef &Sort, const SMTExprRef &AST,
llvm::APFloat &Float, bool useSemantics) {
assert(Sort->isFloatSort() && "Unsupported sort to floating-point!");
@@ -846,7 +750,7 @@ public:
}
bool getInterpretation(const SMTExprRef &Exp, llvm::APSInt &Int) override {
- Z3Model Model = getModel();
+ Z3Model Model(Context, Z3_solver_get_model(Context.Context, Solver));
Z3_func_decl Func = Z3_get_app_decl(
Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST));
if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE)
@@ -860,7 +764,7 @@ public:
}
bool getInterpretation(const SMTExprRef &Exp, llvm::APFloat &Float) override {
- Z3Model Model = getModel();
+ Z3Model Model(Context, Z3_solver_get_model(Context.Context, Solver));
Z3_func_decl Func = Z3_get_app_decl(
Context.Context, Z3_to_app(Context.Context, toZ3Expr(*Exp).AST));
if (Z3_model_has_interp(Context.Context, Model.Model, Func) != Z3_L_TRUE)
@@ -873,7 +777,7 @@ public:
return toAPFloat(Sort, Assign, Float, true);
}
- ConditionTruthVal check() const override {
+ Optional<bool> check() const override {
Z3_lbool res = Z3_solver_check(Context.Context, Solver);
if (res == Z3_L_TRUE)
return true;
@@ -881,7 +785,7 @@ public:
if (res == Z3_L_FALSE)
return false;
- return ConditionTruthVal();
+ return Optional<bool>();
}
void push() override { return Z3_solver_push(Context.Context, Solver); }
@@ -891,138 +795,34 @@ public:
return Z3_solver_pop(Context.Context, Solver, NumStates);
}
- /// Get a model from the solver. Caller should check the model is
- /// satisfiable.
- Z3Model getModel() {
- return Z3Model(Context, Z3_solver_get_model(Context.Context, Solver));
- }
+ bool isFPSupported() override { return true; }
/// Reset the solver and remove all constraints.
- void reset() const override { Z3_solver_reset(Context.Context, Solver); }
+ void reset() override { Z3_solver_reset(Context.Context, Solver); }
void print(raw_ostream &OS) const override {
OS << Z3_solver_to_string(Context.Context, Solver);
}
}; // end class Z3Solver
-class Z3ConstraintManager : public SMTConstraintManager {
+class Z3ConstraintManager : public SMTConstraintManager<ConstraintZ3, Z3Expr> {
SMTSolverRef Solver = CreateZ3Solver();
public:
Z3ConstraintManager(SubEngine *SE, SValBuilder &SB)
: SMTConstraintManager(SE, SB, Solver) {}
-
- void addStateConstraints(ProgramStateRef State) const override {
- // TODO: Don't add all the constraints, only the relevant ones
- ConstraintZ3Ty CZ = State->get<ConstraintZ3>();
- ConstraintZ3Ty::iterator I = CZ.begin(), IE = CZ.end();
-
- // Construct the logical AND of all the constraints
- if (I != IE) {
- std::vector<SMTExprRef> ASTs;
-
- SMTExprRef Constraint = Solver->newExprRef(I++->second);
- while (I != IE) {
- Constraint = Solver->mkAnd(Constraint, Solver->newExprRef(I++->second));
- }
-
- Solver->addConstraint(Constraint);
- }
- }
-
- bool canReasonAbout(SVal X) const override {
- const TargetInfo &TI = getBasicVals().getContext().getTargetInfo();
-
- Optional<nonloc::SymbolVal> SymVal = X.getAs<nonloc::SymbolVal>();
- if (!SymVal)
- return true;
-
- const SymExpr *Sym = SymVal->getSymbol();
- QualType Ty = Sym->getType();
-
- // Complex types are not modeled
- if (Ty->isComplexType() || Ty->isComplexIntegerType())
- return false;
-
- // Non-IEEE 754 floating-point types are not modeled
- if ((Ty->isSpecificBuiltinType(BuiltinType::LongDouble) &&
- (&TI.getLongDoubleFormat() == &llvm::APFloat::x87DoubleExtended() ||
- &TI.getLongDoubleFormat() == &llvm::APFloat::PPCDoubleDouble())))
- return false;
-
- if (isa<SymbolData>(Sym))
- return true;
-
- SValBuilder &SVB = getSValBuilder();
-
- if (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym))
- return canReasonAbout(SVB.makeSymbolVal(SC->getOperand()));
-
- if (const BinarySymExpr *BSE = dyn_cast<BinarySymExpr>(Sym)) {
- if (const SymIntExpr *SIE = dyn_cast<SymIntExpr>(BSE))
- return canReasonAbout(SVB.makeSymbolVal(SIE->getLHS()));
-
- if (const IntSymExpr *ISE = dyn_cast<IntSymExpr>(BSE))
- return canReasonAbout(SVB.makeSymbolVal(ISE->getRHS()));
-
- if (const SymSymExpr *SSE = dyn_cast<SymSymExpr>(BSE))
- return canReasonAbout(SVB.makeSymbolVal(SSE->getLHS())) &&
- canReasonAbout(SVB.makeSymbolVal(SSE->getRHS()));
- }
-
- llvm_unreachable("Unsupported expression to reason about!");
- }
-
- ProgramStateRef removeDeadBindings(ProgramStateRef State,
- SymbolReaper &SymReaper) override {
- ConstraintZ3Ty CZ = State->get<ConstraintZ3>();
- ConstraintZ3Ty::Factory &CZFactory = State->get_context<ConstraintZ3>();
-
- for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) {
- if (SymReaper.maybeDead(I->first))
- CZ = CZFactory.remove(CZ, *I);
- }
-
- return State->set<ConstraintZ3>(CZ);
- }
-
- ProgramStateRef assumeExpr(ProgramStateRef State, SymbolRef Sym,
- const SMTExprRef &Exp) override {
- // Check the model, avoid simplifying AST to save time
- if (checkModel(State, Exp).isConstrainedTrue())
- return State->add<ConstraintZ3>(std::make_pair(Sym, toZ3Expr(*Exp)));
-
- return nullptr;
- }
-
- //==------------------------------------------------------------------------==/
- // Pretty-printing.
- //==------------------------------------------------------------------------==/
-
- void print(ProgramStateRef St, raw_ostream &OS, const char *nl,
- const char *sep) override {
-
- ConstraintZ3Ty CZ = St->get<ConstraintZ3>();
-
- OS << nl << sep << "Constraints:";
- for (ConstraintZ3Ty::iterator I = CZ.begin(), E = CZ.end(); I != E; ++I) {
- OS << nl << ' ' << I->first << " : ";
- I->second.print(OS);
- }
- OS << nl;
- }
}; // end class Z3ConstraintManager
} // end anonymous namespace
#endif
-std::unique_ptr<SMTSolver> clang::ento::CreateZ3Solver() {
+SMTSolverRef clang::ento::CreateZ3Solver() {
#if CLANG_ANALYZER_WITH_Z3
return llvm::make_unique<Z3Solver>();
#else
llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild "
- "with -DCLANG_ANALYZER_BUILD_Z3=ON",
+ "with -DCLANG_ANALYZER_ENABLE_Z3_SOLVER=ON",
false);
return nullptr;
#endif
@@ -1034,7 +834,7 @@ ento::CreateZ3ConstraintManager(ProgramStateManager &StMgr, SubEngine *Eng) {
return llvm::make_unique<Z3ConstraintManager>(Eng, StMgr.getSValBuilder());
#else
llvm::report_fatal_error("Clang was not compiled with Z3 support, rebuild "
- "with -DCLANG_ANALYZER_BUILD_Z3=ON",
+ "with -DCLANG_ANALYZER_ENABLE_Z3_SOLVER=ON",
false);
return nullptr;
#endif
diff --git a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
index 44abde5da6d1..d87937d9b63d 100644
--- a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
+++ b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp
@@ -50,8 +50,6 @@ using namespace ento;
#define DEBUG_TYPE "AnalysisConsumer"
-static std::unique_ptr<ExplodedNode::Auditor> CreateUbiViz();
-
STATISTIC(NumFunctionTopLevel, "The # of functions at top level.");
STATISTIC(NumFunctionsAnalyzed,
"The # of functions and blocks analyzed (as top level "
@@ -206,7 +204,7 @@ public:
PP(CI.getPreprocessor()), OutDir(outdir), Opts(std::move(opts)),
Plugins(plugins), Injector(injector), CTU(CI) {
DigestAnalyzerOptions();
- if (Opts->PrintStats || Opts->shouldSerializeStats()) {
+ if (Opts->PrintStats || Opts->ShouldSerializeStats) {
AnalyzerTimers = llvm::make_unique<llvm::TimerGroup>(
"analyzer", "Analyzer timers");
TUTotalTimer = llvm::make_unique<llvm::Timer>(
@@ -295,13 +293,12 @@ public:
void Initialize(ASTContext &Context) override {
Ctx = &Context;
- checkerMgr =
- createCheckerManager(*Opts, PP.getLangOpts(), Plugins,
- CheckerRegistrationFns, PP.getDiagnostics());
+ checkerMgr = createCheckerManager(
+ *Ctx, *Opts, Plugins, CheckerRegistrationFns, PP.getDiagnostics());
Mgr = llvm::make_unique<AnalysisManager>(
- *Ctx, PP.getDiagnostics(), PP.getLangOpts(), PathConsumers,
- CreateStoreMgr, CreateConstraintMgr, checkerMgr.get(), *Opts, Injector);
+ *Ctx, PP.getDiagnostics(), PathConsumers, CreateStoreMgr,
+ CreateConstraintMgr, checkerMgr.get(), *Opts, Injector);
}
/// Store the top level decls in the set to be processed later on.
@@ -334,9 +331,6 @@ public:
void RunPathSensitiveChecks(Decl *D,
ExprEngine::InliningModes IMode,
SetOfConstDecls *VisitedCallees);
- void ActionExprEngine(Decl *D, bool ObjCGCEnabled,
- ExprEngine::InliningModes IMode,
- SetOfConstDecls *VisitedCallees);
/// Visitors for the RecursiveASTVisitor.
bool shouldWalkTypesOfTypeLocs() const { return false; }
@@ -682,7 +676,7 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) {
// - System headers: don't run any checks.
SourceManager &SM = Ctx->getSourceManager();
const Stmt *Body = D->getBody();
- SourceLocation SL = Body ? Body->getLocStart() : D->getLocation();
+ SourceLocation SL = Body ? Body->getBeginLoc() : D->getLocation();
SL = SM.getExpansionLoc(SL);
if (!Opts->AnalyzeAll && !Mgr->isInCodeFile(SL)) {
@@ -729,9 +723,9 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode,
// Path-sensitive checking.
//===----------------------------------------------------------------------===//
-void AnalysisConsumer::ActionExprEngine(Decl *D, bool ObjCGCEnabled,
- ExprEngine::InliningModes IMode,
- SetOfConstDecls *VisitedCallees) {
+void AnalysisConsumer::RunPathSensitiveChecks(Decl *D,
+ ExprEngine::InliningModes IMode,
+ SetOfConstDecls *VisitedCallees) {
// Construct the analysis engine. First check if the CFG is valid.
// FIXME: Inter-procedural analysis will need to handle invalid CFGs.
if (!Mgr->getCFG(D))
@@ -741,23 +735,14 @@ void AnalysisConsumer::ActionExprEngine(Decl *D, bool ObjCGCEnabled,
if (!Mgr->getAnalysisDeclContext(D)->getAnalysis<RelaxedLiveVariables>())
return;
- ExprEngine Eng(CTU, *Mgr, ObjCGCEnabled, VisitedCallees, &FunctionSummaries,
- IMode);
-
- // Set the graph auditor.
- std::unique_ptr<ExplodedNode::Auditor> Auditor;
- if (Mgr->options.visualizeExplodedGraphWithUbiGraph) {
- Auditor = CreateUbiViz();
- ExplodedNode::SetAuditor(Auditor.get());
- }
+ ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode);
// Execute the worklist algorithm.
Eng.ExecuteWorkList(Mgr->getAnalysisDeclContextManager().getStackFrame(D),
- Mgr->options.getMaxNodesPerTopLevelFunction());
+ Mgr->options.MaxNodesPerTopLevelFunction);
- // Release the auditor (if any) so that it doesn't monitor the graph
- // created BugReporter.
- ExplodedNode::SetAuditor(nullptr);
+ if (!Mgr->options.DumpExplodedGraphTo.empty())
+ Eng.DumpGraph(Mgr->options.TrimGraph, Mgr->options.DumpExplodedGraphTo);
// Visualize the exploded graph.
if (Mgr->options.visualizeExplodedGraphWithGraphViz)
@@ -767,26 +752,6 @@ void AnalysisConsumer::ActionExprEngine(Decl *D, bool ObjCGCEnabled,
Eng.getBugReporter().FlushReports();
}
-void AnalysisConsumer::RunPathSensitiveChecks(Decl *D,
- ExprEngine::InliningModes IMode,
- SetOfConstDecls *Visited) {
-
- switch (Mgr->getLangOpts().getGC()) {
- case LangOptions::NonGC:
- ActionExprEngine(D, false, IMode, Visited);
- break;
-
- case LangOptions::GCOnly:
- ActionExprEngine(D, true, IMode, Visited);
- break;
-
- case LangOptions::HybridGC:
- ActionExprEngine(D, false, IMode, Visited);
- ActionExprEngine(D, true, IMode, Visited);
- break;
- }
-}
-
//===----------------------------------------------------------------------===//
// AnalysisConsumer creation.
//===----------------------------------------------------------------------===//
@@ -804,98 +769,3 @@ ento::CreateAnalysisConsumer(CompilerInstance &CI) {
CI.getFrontendOpts().Plugins,
hasModelPath ? new ModelInjector(CI) : nullptr);
}
-
-//===----------------------------------------------------------------------===//
-// Ubigraph Visualization. FIXME: Move to separate file.
-//===----------------------------------------------------------------------===//
-
-namespace {
-
-class UbigraphViz : public ExplodedNode::Auditor {
- std::unique_ptr<raw_ostream> Out;
- std::string Filename;
- unsigned Cntr;
-
- typedef llvm::DenseMap<void*,unsigned> VMap;
- VMap M;
-
-public:
- UbigraphViz(std::unique_ptr<raw_ostream> Out, StringRef Filename);
-
- ~UbigraphViz() override;
-
- void AddEdge(ExplodedNode *Src, ExplodedNode *Dst) override;
-};
-
-} // end anonymous namespace
-
-static std::unique_ptr<ExplodedNode::Auditor> CreateUbiViz() {
- SmallString<128> P;
- int FD;
- llvm::sys::fs::createTemporaryFile("llvm_ubi", "", FD, P);
- llvm::errs() << "Writing '" << P << "'.\n";
-
- auto Stream = llvm::make_unique<llvm::raw_fd_ostream>(FD, true);
-
- return llvm::make_unique<UbigraphViz>(std::move(Stream), P);
-}
-
-void UbigraphViz::AddEdge(ExplodedNode *Src, ExplodedNode *Dst) {
-
- assert (Src != Dst && "Self-edges are not allowed.");
-
- // Lookup the Src. If it is a new node, it's a root.
- VMap::iterator SrcI= M.find(Src);
- unsigned SrcID;
-
- if (SrcI == M.end()) {
- M[Src] = SrcID = Cntr++;
- *Out << "('vertex', " << SrcID << ", ('color','#00ff00'))\n";
- }
- else
- SrcID = SrcI->second;
-
- // Lookup the Dst.
- VMap::iterator DstI= M.find(Dst);
- unsigned DstID;
-
- if (DstI == M.end()) {
- M[Dst] = DstID = Cntr++;
- *Out << "('vertex', " << DstID << ")\n";
- }
- else {
- // We have hit DstID before. Change its style to reflect a cache hit.
- DstID = DstI->second;
- *Out << "('change_vertex_style', " << DstID << ", 1)\n";
- }
-
- // Add the edge.
- *Out << "('edge', " << SrcID << ", " << DstID
- << ", ('arrow','true'), ('oriented', 'true'))\n";
-}
-
-UbigraphViz::UbigraphViz(std::unique_ptr<raw_ostream> OutStream,
- StringRef Filename)
- : Out(std::move(OutStream)), Filename(Filename), Cntr(0) {
-
- *Out << "('vertex_style_attribute', 0, ('shape', 'icosahedron'))\n";
- *Out << "('vertex_style', 1, 0, ('shape', 'sphere'), ('color', '#ffcc66'),"
- " ('size', '1.5'))\n";
-}
-
-UbigraphViz::~UbigraphViz() {
- Out.reset();
- llvm::errs() << "Running 'ubiviz' program... ";
- std::string ErrMsg;
- std::string Ubiviz;
- if (auto Path = llvm::sys::findProgramByName("ubiviz"))
- Ubiviz = *Path;
- std::array<StringRef, 2> Args{{Ubiviz, Filename}};
-
- if (llvm::sys::ExecuteAndWait(Ubiviz, Args, llvm::None, {}, 0, 0, &ErrMsg)) {
- llvm::errs() << "Error viewing graph: " << ErrMsg << "\n";
- }
-
- // Delete the file.
- llvm::sys::fs::remove(Filename);
-}
diff --git a/lib/StaticAnalyzer/Frontend/CMakeLists.txt b/lib/StaticAnalyzer/Frontend/CMakeLists.txt
index ff0a6e19fc97..5e7dd8f18cd7 100644
--- a/lib/StaticAnalyzer/Frontend/CMakeLists.txt
+++ b/lib/StaticAnalyzer/Frontend/CMakeLists.txt
@@ -7,6 +7,7 @@ set(LLVM_LINK_COMPONENTS
add_clang_library(clangStaticAnalyzerFrontend
AnalysisConsumer.cpp
CheckerRegistration.cpp
+ CheckerRegistry.cpp
FrontendActions.cpp
ModelConsumer.cpp
ModelInjector.cpp
diff --git a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp
index a260c2d85b11..1c31c35b75e4 100644
--- a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp
+++ b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp
@@ -14,146 +14,124 @@
#include "clang/StaticAnalyzer/Frontend/CheckerRegistration.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Frontend/FrontendDiagnostic.h"
-#include "clang/StaticAnalyzer/Checkers/ClangCheckers.h"
#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
-#include "clang/StaticAnalyzer/Core/CheckerOptInfo.h"
-#include "clang/StaticAnalyzer/Core/CheckerRegistry.h"
+#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/StaticAnalyzer/Frontend/FrontendActions.h"
#include "llvm/ADT/SmallVector.h"
-#include "llvm/Support/DynamicLibrary.h"
-#include "llvm/Support/Path.h"
+#include "llvm/Support/FormattedStream.h"
#include "llvm/Support/raw_ostream.h"
#include <memory>
using namespace clang;
using namespace ento;
-using llvm::sys::DynamicLibrary;
-
-namespace {
-class ClangCheckerRegistry : public CheckerRegistry {
- typedef void (*RegisterCheckersFn)(CheckerRegistry &);
-
- static bool isCompatibleAPIVersion(const char *versionString);
- static void warnIncompatible(DiagnosticsEngine *diags, StringRef pluginPath,
- const char *pluginAPIVersion);
-
-public:
- ClangCheckerRegistry(ArrayRef<std::string> plugins,
- DiagnosticsEngine *diags = nullptr);
-};
-
-} // end anonymous namespace
-
-ClangCheckerRegistry::ClangCheckerRegistry(ArrayRef<std::string> plugins,
- DiagnosticsEngine *diags) {
- registerBuiltinCheckers(*this);
-
- for (ArrayRef<std::string>::iterator i = plugins.begin(), e = plugins.end();
- i != e; ++i) {
- // Get access to the plugin.
- std::string err;
- DynamicLibrary lib = DynamicLibrary::getPermanentLibrary(i->c_str(), &err);
- if (!lib.isValid()) {
- diags->Report(diag::err_fe_unable_to_load_plugin) << *i << err;
- continue;
- }
-
- // See if it's compatible with this build of clang.
- const char *pluginAPIVersion =
- (const char *) lib.getAddressOfSymbol("clang_analyzerAPIVersionString");
- if (!isCompatibleAPIVersion(pluginAPIVersion)) {
- warnIncompatible(diags, *i, pluginAPIVersion);
- continue;
- }
-
- // Register its checkers.
- RegisterCheckersFn registerPluginCheckers =
- (RegisterCheckersFn) (intptr_t) lib.getAddressOfSymbol(
- "clang_registerCheckers");
- if (registerPluginCheckers)
- registerPluginCheckers(*this);
- }
-}
-
-bool ClangCheckerRegistry::isCompatibleAPIVersion(const char *versionString) {
- // If the version string is null, it's not an analyzer plugin.
- if (!versionString)
- return false;
-
- // For now, none of the static analyzer API is considered stable.
- // Versions must match exactly.
- return strcmp(versionString, CLANG_ANALYZER_API_VERSION_STRING) == 0;
-}
-
-void ClangCheckerRegistry::warnIncompatible(DiagnosticsEngine *diags,
- StringRef pluginPath,
- const char *pluginAPIVersion) {
- if (!diags)
- return;
- if (!pluginAPIVersion)
- return;
-
- diags->Report(diag::warn_incompatible_analyzer_plugin_api)
- << llvm::sys::path::filename(pluginPath);
- diags->Report(diag::note_incompatible_analyzer_plugin_api)
- << CLANG_ANALYZER_API_VERSION_STRING
- << pluginAPIVersion;
-}
-
-static SmallVector<CheckerOptInfo, 8>
-getCheckerOptList(const AnalyzerOptions &opts) {
- SmallVector<CheckerOptInfo, 8> checkerOpts;
- for (unsigned i = 0, e = opts.CheckersControlList.size(); i != e; ++i) {
- const std::pair<std::string, bool> &opt = opts.CheckersControlList[i];
- checkerOpts.push_back(CheckerOptInfo(opt.first, opt.second));
- }
- return checkerOpts;
-}
std::unique_ptr<CheckerManager> ento::createCheckerManager(
- AnalyzerOptions &opts, const LangOptions &langOpts,
+ ASTContext &context,
+ AnalyzerOptions &opts,
ArrayRef<std::string> plugins,
ArrayRef<std::function<void(CheckerRegistry &)>> checkerRegistrationFns,
DiagnosticsEngine &diags) {
- std::unique_ptr<CheckerManager> checkerMgr(
- new CheckerManager(langOpts, opts));
-
- SmallVector<CheckerOptInfo, 8> checkerOpts = getCheckerOptList(opts);
+ auto checkerMgr = llvm::make_unique<CheckerManager>(context, opts);
- ClangCheckerRegistry allCheckers(plugins, &diags);
+ CheckerRegistry allCheckers(plugins, diags);
for (const auto &Fn : checkerRegistrationFns)
Fn(allCheckers);
- allCheckers.initializeManager(*checkerMgr, checkerOpts);
- allCheckers.validateCheckerOptions(opts, diags);
+ allCheckers.initializeManager(*checkerMgr, opts);
+ allCheckers.validateCheckerOptions(opts);
checkerMgr->finishedCheckerRegistration();
- for (unsigned i = 0, e = checkerOpts.size(); i != e; ++i) {
- if (checkerOpts[i].isUnclaimed()) {
- diags.Report(diag::err_unknown_analyzer_checker)
- << checkerOpts[i].getName();
- diags.Report(diag::note_suggest_disabling_all_checkers);
- }
-
- }
-
return checkerMgr;
}
-void ento::printCheckerHelp(raw_ostream &out, ArrayRef<std::string> plugins) {
+void ento::printCheckerHelp(raw_ostream &out, ArrayRef<std::string> plugins,
+ DiagnosticsEngine &diags) {
out << "OVERVIEW: Clang Static Analyzer Checkers List\n\n";
out << "USAGE: -analyzer-checker <CHECKER or PACKAGE,...>\n\n";
- ClangCheckerRegistry(plugins).printHelp(out);
+ CheckerRegistry(plugins, diags).printHelp(out);
}
void ento::printEnabledCheckerList(raw_ostream &out,
ArrayRef<std::string> plugins,
- const AnalyzerOptions &opts) {
+ const AnalyzerOptions &opts,
+ DiagnosticsEngine &diags) {
out << "OVERVIEW: Clang Static Analyzer Enabled Checkers List\n\n";
- SmallVector<CheckerOptInfo, 8> checkerOpts = getCheckerOptList(opts);
- ClangCheckerRegistry(plugins).printList(out, checkerOpts);
+ CheckerRegistry(plugins, diags).printList(out, opts);
+}
+
+void ento::printAnalyzerConfigList(raw_ostream &out) {
+ out << "OVERVIEW: Clang Static Analyzer -analyzer-config Option List\n\n";
+ out << "USAGE: clang -cc1 [CLANG_OPTIONS] -analyzer-config "
+ "<OPTION1=VALUE,OPTION2=VALUE,...>\n\n";
+ out << " clang -cc1 [CLANG_OPTIONS] -analyzer-config OPTION1=VALUE, "
+ "-analyzer-config OPTION2=VALUE, ...\n\n";
+ out << " clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang"
+ "<OPTION1=VALUE,OPTION2=VALUE,...>\n\n";
+ out << " clang [CLANG_OPTIONS] -Xclang -analyzer-config -Xclang "
+ "OPTION1=VALUE, -Xclang -analyzer-config -Xclang "
+ "OPTION2=VALUE, ...\n\n";
+ out << "OPTIONS:\n\n";
+
+ using OptionAndDescriptionTy = std::pair<StringRef, std::string>;
+ OptionAndDescriptionTy PrintableOptions[] = {
+#define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \
+ { \
+ CMDFLAG, \
+ llvm::Twine(llvm::Twine() + "(" + \
+ (StringRef(#TYPE) == "StringRef" ? "string" : #TYPE ) + \
+ ") " DESC \
+ " (default: " #DEFAULT_VAL ")").str() \
+ },
+
+#define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \
+ SHALLOW_VAL, DEEP_VAL) \
+ { \
+ CMDFLAG, \
+ llvm::Twine(llvm::Twine() + "(" + \
+ (StringRef(#TYPE) == "StringRef" ? "string" : #TYPE ) + \
+ ") " DESC \
+ " (default: " #SHALLOW_VAL " in shallow mode, " #DEEP_VAL \
+ " in deep mode)").str() \
+ },
+#include "clang/StaticAnalyzer/Core/AnalyzerOptions.def"
+#undef ANALYZER_OPTION
+#undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE
+ };
+
+ llvm::sort(PrintableOptions, [](const OptionAndDescriptionTy &LHS,
+ const OptionAndDescriptionTy &RHS) {
+ return LHS.first < RHS.first;
+ });
+
+ constexpr size_t MinLineWidth = 70;
+ constexpr size_t PadForOpt = 2;
+ constexpr size_t OptionWidth = 30;
+ constexpr size_t PadForDesc = PadForOpt + OptionWidth;
+ static_assert(MinLineWidth > PadForDesc, "MinLineWidth must be greater!");
+
+ llvm::formatted_raw_ostream FOut(out);
+
+ for (const auto &Pair : PrintableOptions) {
+ FOut.PadToColumn(PadForOpt) << Pair.first;
+
+ // If the buffer's length is greater then PadForDesc, print a newline.
+ if (FOut.getColumn() > PadForDesc)
+ FOut << '\n';
+
+ FOut.PadToColumn(PadForDesc);
+
+ for (char C : Pair.second) {
+ if (FOut.getColumn() > MinLineWidth && C == ' ') {
+ FOut << '\n';
+ FOut.PadToColumn(PadForDesc);
+ continue;
+ }
+ FOut << C;
+ }
+ FOut << "\n\n";
+ }
}
diff --git a/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp
new file mode 100644
index 000000000000..620c0e588906
--- /dev/null
+++ b/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp
@@ -0,0 +1,247 @@
+//===- CheckerRegistry.cpp - Maintains all available checkers -------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Frontend/FrontendDiagnostic.h"
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SetVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/raw_ostream.h"
+#include <algorithm>
+
+using namespace clang;
+using namespace ento;
+using llvm::sys::DynamicLibrary;
+
+using RegisterCheckersFn = void (*)(CheckerRegistry &);
+
+static bool isCompatibleAPIVersion(const char *versionString) {
+ // If the version string is null, it's not an analyzer plugin.
+ if (!versionString)
+ return false;
+
+ // For now, none of the static analyzer API is considered stable.
+ // Versions must match exactly.
+ return strcmp(versionString, CLANG_ANALYZER_API_VERSION_STRING) == 0;
+}
+
+CheckerRegistry::CheckerRegistry(ArrayRef<std::string> plugins,
+ DiagnosticsEngine &diags) : Diags(diags) {
+#define GET_CHECKERS
+#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI) \
+ addChecker(register##CLASS, FULLNAME, HELPTEXT, DOC_URI);
+#include "clang/StaticAnalyzer/Checkers/Checkers.inc"
+#undef CHECKER
+#undef GET_CHECKERS
+
+ for (ArrayRef<std::string>::iterator i = plugins.begin(), e = plugins.end();
+ i != e; ++i) {
+ // Get access to the plugin.
+ std::string err;
+ DynamicLibrary lib = DynamicLibrary::getPermanentLibrary(i->c_str(), &err);
+ if (!lib.isValid()) {
+ diags.Report(diag::err_fe_unable_to_load_plugin) << *i << err;
+ continue;
+ }
+
+ // See if it's compatible with this build of clang.
+ const char *pluginAPIVersion =
+ (const char *) lib.getAddressOfSymbol("clang_analyzerAPIVersionString");
+ if (!isCompatibleAPIVersion(pluginAPIVersion)) {
+ Diags.Report(diag::warn_incompatible_analyzer_plugin_api)
+ << llvm::sys::path::filename(*i);
+ Diags.Report(diag::note_incompatible_analyzer_plugin_api)
+ << CLANG_ANALYZER_API_VERSION_STRING
+ << pluginAPIVersion;
+ continue;
+ }
+
+ // Register its checkers.
+ RegisterCheckersFn registerPluginCheckers =
+ (RegisterCheckersFn) (intptr_t) lib.getAddressOfSymbol(
+ "clang_registerCheckers");
+ if (registerPluginCheckers)
+ registerPluginCheckers(*this);
+ }
+}
+
+static constexpr char PackageSeparator = '.';
+
+static bool checkerNameLT(const CheckerRegistry::CheckerInfo &a,
+ const CheckerRegistry::CheckerInfo &b) {
+ return a.FullName < b.FullName;
+}
+
+static bool isInPackage(const CheckerRegistry::CheckerInfo &checker,
+ StringRef packageName) {
+ // Does the checker's full name have the package as a prefix?
+ if (!checker.FullName.startswith(packageName))
+ return false;
+
+ // Is the package actually just the name of a specific checker?
+ if (checker.FullName.size() == packageName.size())
+ return true;
+
+ // Is the checker in the package (or a subpackage)?
+ if (checker.FullName[packageName.size()] == PackageSeparator)
+ return true;
+
+ return false;
+}
+
+CheckerRegistry::CheckerInfoSet CheckerRegistry::getEnabledCheckers(
+ const AnalyzerOptions &Opts) const {
+
+ assert(std::is_sorted(Checkers.begin(), Checkers.end(), checkerNameLT) &&
+ "In order to efficiently gather checkers, this function expects them "
+ "to be already sorted!");
+
+ CheckerInfoSet enabledCheckers;
+ const auto end = Checkers.cend();
+
+ for (const std::pair<std::string, bool> &opt : Opts.CheckersControlList) {
+ // Use a binary search to find the possible start of the package.
+ CheckerRegistry::CheckerInfo packageInfo(nullptr, opt.first, "", "");
+ auto firstRelatedChecker =
+ std::lower_bound(Checkers.cbegin(), end, packageInfo, checkerNameLT);
+
+ if (firstRelatedChecker == end ||
+ !isInPackage(*firstRelatedChecker, opt.first)) {
+ Diags.Report(diag::err_unknown_analyzer_checker) << opt.first;
+ Diags.Report(diag::note_suggest_disabling_all_checkers);
+ return {};
+ }
+
+ // See how large the package is.
+ // If the package doesn't exist, assume the option refers to a single
+ // checker.
+ size_t size = 1;
+ llvm::StringMap<size_t>::const_iterator packageSize =
+ Packages.find(opt.first);
+ if (packageSize != Packages.end())
+ size = packageSize->getValue();
+
+ // Step through all the checkers in the package.
+ for (auto lastRelatedChecker = firstRelatedChecker+size;
+ firstRelatedChecker != lastRelatedChecker; ++firstRelatedChecker)
+ if (opt.second)
+ enabledCheckers.insert(&*firstRelatedChecker);
+ else
+ enabledCheckers.remove(&*firstRelatedChecker);
+ }
+
+ return enabledCheckers;
+}
+
+void CheckerRegistry::addChecker(InitializationFunction Fn, StringRef Name,
+ StringRef Desc, StringRef DocsUri) {
+ Checkers.emplace_back(Fn, Name, Desc, DocsUri);
+
+ // Record the presence of the checker in its packages.
+ StringRef packageName, leafName;
+ std::tie(packageName, leafName) = Name.rsplit(PackageSeparator);
+ while (!leafName.empty()) {
+ Packages[packageName] += 1;
+ std::tie(packageName, leafName) = packageName.rsplit(PackageSeparator);
+ }
+}
+
+void CheckerRegistry::initializeManager(CheckerManager &checkerMgr,
+ const AnalyzerOptions &Opts) const {
+ // Sort checkers for efficient collection.
+ llvm::sort(Checkers, checkerNameLT);
+
+ // Collect checkers enabled by the options.
+ CheckerInfoSet enabledCheckers = getEnabledCheckers(Opts);
+
+ // Initialize the CheckerManager with all enabled checkers.
+ for (const auto *i : enabledCheckers) {
+ checkerMgr.setCurrentCheckName(CheckName(i->FullName));
+ i->Initialize(checkerMgr);
+ }
+}
+
+void CheckerRegistry::validateCheckerOptions(
+ const AnalyzerOptions &opts) const {
+ for (const auto &config : opts.Config) {
+ size_t pos = config.getKey().find(':');
+ if (pos == StringRef::npos)
+ continue;
+
+ bool hasChecker = false;
+ StringRef checkerName = config.getKey().substr(0, pos);
+ for (const auto &checker : Checkers) {
+ if (checker.FullName.startswith(checkerName) &&
+ (checker.FullName.size() == pos || checker.FullName[pos] == '.')) {
+ hasChecker = true;
+ break;
+ }
+ }
+ if (!hasChecker)
+ Diags.Report(diag::err_unknown_analyzer_checker) << checkerName;
+ }
+}
+
+void CheckerRegistry::printHelp(raw_ostream &out,
+ size_t maxNameChars) const {
+ // FIXME: Alphabetical sort puts 'experimental' in the middle.
+ // Would it be better to name it '~experimental' or something else
+ // that's ASCIIbetically last?
+ llvm::sort(Checkers, checkerNameLT);
+
+ // FIXME: Print available packages.
+
+ out << "CHECKERS:\n";
+
+ // Find the maximum option length.
+ size_t optionFieldWidth = 0;
+ for (const auto &i : Checkers) {
+ // Limit the amount of padding we are willing to give up for alignment.
+ // Package.Name Description [Hidden]
+ size_t nameLength = i.FullName.size();
+ if (nameLength <= maxNameChars)
+ optionFieldWidth = std::max(optionFieldWidth, nameLength);
+ }
+
+ const size_t initialPad = 2;
+ for (const auto &i : Checkers) {
+ out.indent(initialPad) << i.FullName;
+
+ int pad = optionFieldWidth - i.FullName.size();
+
+ // Break on long option names.
+ if (pad < 0) {
+ out << '\n';
+ pad = optionFieldWidth + initialPad;
+ }
+ out.indent(pad + 2) << i.Desc;
+
+ out << '\n';
+ }
+}
+
+void CheckerRegistry::printList(raw_ostream &out,
+ const AnalyzerOptions &opts) const {
+ // Sort checkers for efficient collection.
+ llvm::sort(Checkers, checkerNameLT);
+
+ // Collect checkers enabled by the options.
+ CheckerInfoSet enabledCheckers = getEnabledCheckers(opts);
+
+ for (const auto *i : enabledCheckers)
+ out << i->FullName << '\n';
+}
diff --git a/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp
index c43d30440c8f..b1927c8401d6 100644
--- a/lib/StaticAnalyzer/Frontend/ModelInjector.cpp
+++ b/lib/StaticAnalyzer/Frontend/ModelInjector.cpp
@@ -48,7 +48,7 @@ void ModelInjector::onBodySynthesis(const NamedDecl *D) {
FileID mainFileID = SM.getMainFileID();
AnalyzerOptionsRef analyzerOpts = CI.getAnalyzerOpts();
- llvm::StringRef modelPath = analyzerOpts->Config["model-path"];
+ llvm::StringRef modelPath = analyzerOpts->ModelPath;
llvm::SmallString<128> fileName;
diff --git a/lib/StaticAnalyzer/README.txt b/lib/StaticAnalyzer/README.txt
index d4310c57d849..79a16ec7673d 100644
--- a/lib/StaticAnalyzer/README.txt
+++ b/lib/StaticAnalyzer/README.txt
@@ -69,23 +69,23 @@ triggered the problem.
= Notes about C++ =
-Since now constructors are seen before the variable that is constructed
-in the CFG, we create a temporary object as the destination region that
+Since now constructors are seen before the variable that is constructed
+in the CFG, we create a temporary object as the destination region that
is constructed into. See ExprEngine::VisitCXXConstructExpr().
In ExprEngine::processCallExit(), we always bind the object region to the
evaluated CXXConstructExpr. Then in VisitDeclStmt(), we compute the
corresponding lazy compound value if the variable is not a reference, and
bind the variable region to the lazy compound value. If the variable
-is a reference, just use the object region as the initilizer value.
+is a reference, just use the object region as the initializer value.
Before entering a C++ method (or ctor/dtor), the 'this' region is bound
-to the object region. In ctors, we synthesize 'this' region with
+to the object region. In ctors, we synthesize 'this' region with
CXXRecordDecl*, which means we do not use type qualifiers. In methods, we
-synthesize 'this' region with CXXMethodDecl*, which has getThisType()
+synthesize 'this' region with CXXMethodDecl*, which has getThisType()
taking type qualifiers into account. It does not matter we use qualified
'this' region in one method and unqualified 'this' region in another
-method, because we only need to ensure the 'this' region is consistent
+method, because we only need to ensure the 'this' region is consistent
when we synthesize it and create it directly from CXXThisExpr in a single
method call.