1 //== ObjCContainersChecker.cpp - Path sensitive checker for CFArray *- C++ -*=//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // Performs path sensitive checks of Core Foundation static containers like
11 // 1) Check for buffer overflows:
12 // In CFArrayGetArrayAtIndex( myArray, index), if the index is outside the
13 // index space of theArray (0 to N-1 inclusive (where N is the count of
14 // theArray), the behavior is undefined.
16 //===----------------------------------------------------------------------===//
18 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19 #include "clang/AST/ParentMap.h"
20 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
21 #include "clang/StaticAnalyzer/Core/Checker.h"
22 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
23 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
24 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
26 using namespace clang
;
30 class ObjCContainersChecker
: public Checker
< check::PreStmt
<CallExpr
>,
31 check::PostStmt
<CallExpr
>,
32 check::PointerEscape
> {
33 mutable std::unique_ptr
<BugType
> BT
;
34 inline void initBugType() const {
36 BT
.reset(new BugType(this, "CFArray API",
37 categories::CoreFoundationObjectiveC
));
40 inline SymbolRef
getArraySym(const Expr
*E
, CheckerContext
&C
) const {
41 SVal ArrayRef
= C
.getSVal(E
);
42 SymbolRef ArraySym
= ArrayRef
.getAsSymbol();
46 void addSizeInfo(const Expr
*Array
, const Expr
*Size
,
47 CheckerContext
&C
) const;
50 void checkPostStmt(const CallExpr
*CE
, CheckerContext
&C
) const;
51 void checkPreStmt(const CallExpr
*CE
, CheckerContext
&C
) const;
52 ProgramStateRef
checkPointerEscape(ProgramStateRef State
,
53 const InvalidatedSymbols
&Escaped
,
54 const CallEvent
*Call
,
55 PointerEscapeKind Kind
) const;
57 void printState(raw_ostream
&OS
, ProgramStateRef State
,
58 const char *NL
, const char *Sep
) const override
;
60 } // end anonymous namespace
62 // ProgramState trait - a map from array symbol to its state.
63 REGISTER_MAP_WITH_PROGRAMSTATE(ArraySizeMap
, SymbolRef
, DefinedSVal
)
65 void ObjCContainersChecker::addSizeInfo(const Expr
*Array
, const Expr
*Size
,
66 CheckerContext
&C
) const {
67 ProgramStateRef State
= C
.getState();
68 SVal SizeV
= C
.getSVal(Size
);
69 // Undefined is reported by another checker.
70 if (SizeV
.isUnknownOrUndef())
73 // Get the ArrayRef symbol.
74 SVal ArrayRef
= C
.getSVal(Array
);
75 SymbolRef ArraySym
= ArrayRef
.getAsSymbol();
80 State
->set
<ArraySizeMap
>(ArraySym
, SizeV
.castAs
<DefinedSVal
>()));
83 void ObjCContainersChecker::checkPostStmt(const CallExpr
*CE
,
84 CheckerContext
&C
) const {
85 StringRef Name
= C
.getCalleeName(CE
);
86 if (Name
.empty() || CE
->getNumArgs() < 1)
89 // Add array size information to the state.
90 if (Name
.equals("CFArrayCreate")) {
91 if (CE
->getNumArgs() < 3)
93 // Note, we can visit the Create method in the post-visit because
94 // the CFIndex parameter is passed in by value and will not be invalidated
96 addSizeInfo(CE
, CE
->getArg(2), C
);
100 if (Name
.equals("CFArrayGetCount")) {
101 addSizeInfo(CE
->getArg(0), CE
, C
);
106 void ObjCContainersChecker::checkPreStmt(const CallExpr
*CE
,
107 CheckerContext
&C
) const {
108 StringRef Name
= C
.getCalleeName(CE
);
109 if (Name
.empty() || CE
->getNumArgs() < 2)
112 // Check the array access.
113 if (Name
.equals("CFArrayGetValueAtIndex")) {
114 ProgramStateRef State
= C
.getState();
115 // Retrieve the size.
116 // Find out if we saw this array symbol before and have information about
118 const Expr
*ArrayExpr
= CE
->getArg(0);
119 SymbolRef ArraySym
= getArraySym(ArrayExpr
, C
);
123 const DefinedSVal
*Size
= State
->get
<ArraySizeMap
>(ArraySym
);
129 const Expr
*IdxExpr
= CE
->getArg(1);
130 SVal IdxVal
= C
.getSVal(IdxExpr
);
131 if (IdxVal
.isUnknownOrUndef())
133 DefinedSVal Idx
= IdxVal
.castAs
<DefinedSVal
>();
135 // Now, check if 'Idx in [0, Size-1]'.
136 const QualType T
= IdxExpr
->getType();
137 ProgramStateRef StInBound
, StOutBound
;
138 std::tie(StInBound
, StOutBound
) = State
->assumeInBoundDual(Idx
, *Size
, T
);
139 if (StOutBound
&& !StInBound
) {
140 ExplodedNode
*N
= C
.generateErrorNode(StOutBound
);
144 auto R
= std::make_unique
<PathSensitiveBugReport
>(
145 *BT
, "Index is out of bounds", N
);
146 R
->addRange(IdxExpr
->getSourceRange());
147 bugreporter::trackExpressionValue(N
, IdxExpr
, *R
,
148 {bugreporter::TrackingKind::Thorough
,
149 /*EnableNullFPSuppression=*/false});
150 C
.emitReport(std::move(R
));
157 ObjCContainersChecker::checkPointerEscape(ProgramStateRef State
,
158 const InvalidatedSymbols
&Escaped
,
159 const CallEvent
*Call
,
160 PointerEscapeKind Kind
) const {
161 for (const auto &Sym
: Escaped
) {
162 // When a symbol for a mutable array escapes, we can't reason precisely
163 // about its size any more -- so remove it from the map.
164 // Note that we aren't notified here when a CFMutableArrayRef escapes as a
165 // CFArrayRef. This is because CFArrayRef is typedef'd as a pointer to a
166 // const-qualified type.
167 State
= State
->remove
<ArraySizeMap
>(Sym
);
172 void ObjCContainersChecker::printState(raw_ostream
&OS
, ProgramStateRef State
,
173 const char *NL
, const char *Sep
) const {
174 ArraySizeMapTy Map
= State
->get
<ArraySizeMap
>();
178 OS
<< Sep
<< "ObjC container sizes :" << NL
;
180 OS
<< I
.first
<< " : " << I
.second
<< NL
;
184 /// Register checker.
185 void ento::registerObjCContainersChecker(CheckerManager
&mgr
) {
186 mgr
.registerChecker
<ObjCContainersChecker
>();
189 bool ento::shouldRegisterObjCContainersChecker(const CheckerManager
&mgr
) {