xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp (revision cf229d575226ccae2742cda0708a9f981f71fdfb)
1 //===- CastValueChecker - Model implementation of custom RTTIs --*- C++ -*-===//
2 //
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
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This defines CastValueChecker which models casts of custom RTTIs.
10 //
11 //===----------------------------------------------------------------------===//
12 
13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
14 #include "clang/StaticAnalyzer/Core/Checker.h"
15 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
16 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
17 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
18 #include "llvm/ADT/Optional.h"
19 #include <utility>
20 
21 using namespace clang;
22 using namespace ento;
23 
24 namespace {
25 class CastValueChecker : public Checker<eval::Call> {
26   enum class CastKind { Function, Method };
27 
28   using CastCheck =
29       std::function<void(const CastValueChecker *, const CallExpr *,
30                          DefinedOrUnknownSVal, CheckerContext &)>;
31 
32   using CheckKindPair = std::pair<CastCheck, CastKind>;
33 
34 public:
35   // We have five cases to evaluate a cast:
36   // 1) The parameter is non-null, the return value is non-null
37   // 2) The parameter is non-null, the return value is null
38   // 3) The parameter is null, the return value is null
39   // cast: 1;  dyn_cast: 1, 2;  cast_or_null: 1, 3;  dyn_cast_or_null: 1, 2, 3.
40   //
41   // 4) castAs: has no parameter, the return value is non-null.
42   // 5) getAs:  has no parameter, the return value is null or non-null.
43   bool evalCall(const CallEvent &Call, CheckerContext &C) const;
44 
45 private:
46   // These are known in the LLVM project. The pairs are in the following form:
47   // {{{namespace, call}, argument-count}, {callback, kind}}
48   const CallDescriptionMap<CheckKindPair> CDM = {
49       {{{"llvm", "cast"}, 1},
50        {&CastValueChecker::evalCast, CastKind::Function}},
51       {{{"llvm", "dyn_cast"}, 1},
52        {&CastValueChecker::evalDynCast, CastKind::Function}},
53       {{{"llvm", "cast_or_null"}, 1},
54        {&CastValueChecker::evalCastOrNull, CastKind::Function}},
55       {{{"llvm", "dyn_cast_or_null"}, 1},
56        {&CastValueChecker::evalDynCastOrNull, CastKind::Function}},
57       {{{"clang", "castAs"}, 0},
58        {&CastValueChecker::evalCastAs, CastKind::Method}},
59       {{{"clang", "getAs"}, 0},
60        {&CastValueChecker::evalGetAs, CastKind::Method}}};
61 
62   void evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV,
63                 CheckerContext &C) const;
64   void evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV,
65                    CheckerContext &C) const;
66   void evalCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV,
67                       CheckerContext &C) const;
68   void evalDynCastOrNull(const CallExpr *CE, DefinedOrUnknownSVal DV,
69                          CheckerContext &C) const;
70   void evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV,
71                   CheckerContext &C) const;
72   void evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV,
73                  CheckerContext &C) const;
74 };
75 } // namespace
76 
77 static std::string getCastName(const Expr *Cast) {
78   QualType Ty = Cast->getType();
79   if (const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl())
80     return RD->getNameAsString();
81 
82   return Ty->getPointeeCXXRecordDecl()->getNameAsString();
83 }
84 
85 static const NoteTag *getCastTag(bool IsNullReturn, const CallExpr *CE,
86                                  CheckerContext &C,
87                                  bool IsCheckedCast = false) {
88   Optional<std::string> CastFromName = (CE->getNumArgs() > 0)
89                                            ? getCastName(CE->getArg(0))
90                                            : Optional<std::string>();
91   std::string CastToName = getCastName(CE);
92 
93   return C.getNoteTag(
94       [CastFromName, CastToName, IsNullReturn,
95        IsCheckedCast](BugReport &) -> std::string {
96         SmallString<128> Msg;
97         llvm::raw_svector_ostream Out(Msg);
98 
99         Out << (!IsCheckedCast ? "Assuming dynamic cast " : "Checked cast ");
100         if (CastFromName)
101           Out << "from '" << *CastFromName << "' ";
102 
103         Out << "to '" << CastToName << "' "
104             << (!IsNullReturn ? "succeeds" : "fails");
105 
106         return Out.str();
107       },
108       /*IsPrunable=*/true);
109 }
110 
111 static ProgramStateRef getState(bool IsNullReturn,
112                                 DefinedOrUnknownSVal ReturnDV,
113                                 const CallExpr *CE, ProgramStateRef State,
114                                 CheckerContext &C) {
115   return State->BindExpr(
116       CE, C.getLocationContext(),
117       IsNullReturn ? C.getSValBuilder().makeNull() : ReturnDV, false);
118 }
119 
120 //===----------------------------------------------------------------------===//
121 // Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null.
122 //===----------------------------------------------------------------------===//
123 
124 static void evalNonNullParamNonNullReturn(const CallExpr *CE,
125                                           DefinedOrUnknownSVal DV,
126                                           CheckerContext &C,
127                                           bool IsCheckedCast = false) {
128   bool IsNullReturn = false;
129   if (ProgramStateRef State = C.getState()->assume(DV, true))
130     C.addTransition(getState(IsNullReturn, DV, CE, State, C),
131                     getCastTag(IsNullReturn, CE, C, IsCheckedCast));
132 }
133 
134 static void evalNonNullParamNullReturn(const CallExpr *CE,
135                                        DefinedOrUnknownSVal DV,
136                                        CheckerContext &C) {
137   bool IsNullReturn = true;
138   if (ProgramStateRef State = C.getState()->assume(DV, true))
139     C.addTransition(getState(IsNullReturn, DV, CE, State, C),
140                     getCastTag(IsNullReturn, CE, C));
141 }
142 
143 static void evalNullParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV,
144                                     CheckerContext &C) {
145   if (ProgramStateRef State = C.getState()->assume(DV, false))
146     C.addTransition(getState(/*IsNullReturn=*/true, DV, CE, State, C),
147                     C.getNoteTag("Assuming null pointer is passed into cast",
148                                  /*IsPrunable=*/true));
149 }
150 
151 void CastValueChecker::evalCast(const CallExpr *CE, DefinedOrUnknownSVal DV,
152                                 CheckerContext &C) const {
153   evalNonNullParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true);
154 }
155 
156 void CastValueChecker::evalDynCast(const CallExpr *CE, DefinedOrUnknownSVal DV,
157                                    CheckerContext &C) const {
158   evalNonNullParamNonNullReturn(CE, DV, C);
159   evalNonNullParamNullReturn(CE, DV, C);
160 }
161 
162 void CastValueChecker::evalCastOrNull(const CallExpr *CE,
163                                       DefinedOrUnknownSVal DV,
164                                       CheckerContext &C) const {
165   evalNonNullParamNonNullReturn(CE, DV, C);
166   evalNullParamNullReturn(CE, DV, C);
167 }
168 
169 void CastValueChecker::evalDynCastOrNull(const CallExpr *CE,
170                                          DefinedOrUnknownSVal DV,
171                                          CheckerContext &C) const {
172   evalNonNullParamNonNullReturn(CE, DV, C);
173   evalNonNullParamNullReturn(CE, DV, C);
174   evalNullParamNullReturn(CE, DV, C);
175 }
176 
177 //===----------------------------------------------------------------------===//
178 // Evaluating castAs, getAs.
179 //===----------------------------------------------------------------------===//
180 
181 static void evalZeroParamNonNullReturn(const CallExpr *CE,
182                                        DefinedOrUnknownSVal DV,
183                                        CheckerContext &C,
184                                        bool IsCheckedCast = false) {
185   bool IsNullReturn = false;
186   if (ProgramStateRef State = C.getState()->assume(DV, true))
187     C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C),
188                     getCastTag(IsNullReturn, CE, C, IsCheckedCast));
189 }
190 
191 static void evalZeroParamNullReturn(const CallExpr *CE, DefinedOrUnknownSVal DV,
192                                     CheckerContext &C) {
193   bool IsNullReturn = true;
194   if (ProgramStateRef State = C.getState()->assume(DV, true))
195     C.addTransition(getState(IsNullReturn, DV, CE, C.getState(), C),
196                     getCastTag(IsNullReturn, CE, C));
197 }
198 
199 void CastValueChecker::evalCastAs(const CallExpr *CE, DefinedOrUnknownSVal DV,
200                                   CheckerContext &C) const {
201   evalZeroParamNonNullReturn(CE, DV, C, /*IsCheckedCast=*/true);
202 }
203 
204 void CastValueChecker::evalGetAs(const CallExpr *CE, DefinedOrUnknownSVal DV,
205                                  CheckerContext &C) const {
206   evalZeroParamNonNullReturn(CE, DV, C);
207   evalZeroParamNullReturn(CE, DV, C);
208 }
209 
210 bool CastValueChecker::evalCall(const CallEvent &Call,
211                                 CheckerContext &C) const {
212   const auto *Lookup = CDM.lookup(Call);
213   if (!Lookup)
214     return false;
215 
216   // If we cannot obtain the call's class we cannot be sure how to model it.
217   QualType ResultTy = Call.getResultType();
218   if (!ResultTy->getPointeeCXXRecordDecl())
219     return false;
220 
221   const CastCheck &Check = Lookup->first;
222   CastKind Kind = Lookup->second;
223 
224   const auto *CE = cast<CallExpr>(Call.getOriginExpr());
225   Optional<DefinedOrUnknownSVal> DV;
226 
227   switch (Kind) {
228   case CastKind::Function: {
229     // If we cannot obtain the arg's class we cannot be sure how to model it.
230     QualType ArgTy = Call.parameters()[0]->getType();
231     if (!ArgTy->getAsCXXRecordDecl() && !ArgTy->getPointeeCXXRecordDecl())
232       return false;
233 
234     DV = Call.getArgSVal(0).getAs<DefinedOrUnknownSVal>();
235     break;
236   }
237   case CastKind::Method:
238     // If we cannot obtain the 'InstanceCall' we cannot be sure how to model it.
239     const auto *InstanceCall = dyn_cast<CXXInstanceCall>(&Call);
240     if (!InstanceCall)
241       return false;
242 
243     DV = InstanceCall->getCXXThisVal().getAs<DefinedOrUnknownSVal>();
244     break;
245   }
246 
247   if (!DV)
248     return false;
249 
250   Check(this, CE, *DV, C);
251   return true;
252 }
253 
254 void ento::registerCastValueChecker(CheckerManager &Mgr) {
255   Mgr.registerChecker<CastValueChecker>();
256 }
257 
258 bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) {
259   return true;
260 }
261