/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===-- UncheckedOptionalAccessModel.cpp ------------------------*- 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 file defines a dataflow analysis that detects unsafe uses of optional |
10 | | // values. |
11 | | // |
12 | | //===----------------------------------------------------------------------===// |
13 | | |
14 | | #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h" |
15 | | #include "clang/AST/ASTContext.h" |
16 | | #include "clang/AST/DeclCXX.h" |
17 | | #include "clang/AST/Expr.h" |
18 | | #include "clang/AST/ExprCXX.h" |
19 | | #include "clang/AST/Stmt.h" |
20 | | #include "clang/ASTMatchers/ASTMatchers.h" |
21 | | #include "clang/ASTMatchers/ASTMatchersMacros.h" |
22 | | #include "clang/Analysis/CFG.h" |
23 | | #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" |
24 | | #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" |
25 | | #include "clang/Analysis/FlowSensitive/NoopLattice.h" |
26 | | #include "clang/Analysis/FlowSensitive/StorageLocation.h" |
27 | | #include "clang/Analysis/FlowSensitive/Value.h" |
28 | | #include "clang/Basic/SourceLocation.h" |
29 | | #include "llvm/ADT/StringRef.h" |
30 | | #include "llvm/Support/Casting.h" |
31 | | #include "llvm/Support/ErrorHandling.h" |
32 | | #include <cassert> |
33 | | #include <memory> |
34 | | #include <optional> |
35 | | #include <utility> |
36 | | #include <vector> |
37 | | |
38 | | namespace clang { |
39 | | namespace dataflow { |
40 | | |
41 | | static bool isTopLevelNamespaceWithName(const NamespaceDecl &NS, |
42 | 5.06k | llvm::StringRef Name) { |
43 | 5.06k | return NS.getDeclName().isIdentifier() && NS.getName() == Name && |
44 | 5.06k | NS.getParent() != nullptr5.05k && NS.getParent()->isTranslationUnit()5.05k ; |
45 | 5.06k | } |
46 | | |
47 | 8.02k | static bool hasOptionalClassName(const CXXRecordDecl &RD) { |
48 | 8.02k | if (!RD.getDeclName().isIdentifier()) |
49 | 0 | return false; |
50 | | |
51 | 8.02k | if (RD.getName() == "optional") { |
52 | 5.08k | if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext())) |
53 | 5.08k | return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl")2.54k ; |
54 | 0 | return false; |
55 | 5.08k | } |
56 | | |
57 | 2.94k | if (RD.getName() == "Optional") { |
58 | | // Check whether namespace is "::base". |
59 | 2.52k | const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); |
60 | 2.52k | return N != nullptr && isTopLevelNamespaceWithName(*N, "base"); |
61 | 2.52k | } |
62 | | |
63 | 419 | return false; |
64 | 2.94k | } |
65 | | |
66 | | namespace { |
67 | | |
68 | | using namespace ::clang::ast_matchers; |
69 | | using LatticeTransferState = TransferState<NoopLattice>; |
70 | | |
71 | 7.50k | AST_MATCHER(CXXRecordDecl, hasOptionalClassNameMatcher) { |
72 | 7.50k | return hasOptionalClassName(Node); |
73 | 7.50k | } |
74 | | |
75 | 14.3k | DeclarationMatcher optionalClass() { |
76 | 14.3k | return classTemplateSpecializationDecl( |
77 | 14.3k | hasOptionalClassNameMatcher(), |
78 | 14.3k | hasTemplateArgument(0, refersToType(type().bind("T")))); |
79 | 14.3k | } |
80 | | |
81 | 9.47k | auto optionalOrAliasType() { |
82 | 9.47k | return hasUnqualifiedDesugaredType( |
83 | 9.47k | recordType(hasDeclaration(optionalClass()))); |
84 | 9.47k | } |
85 | | |
86 | | /// Matches any of the spellings of the optional types and sugar, aliases, etc. |
87 | 6.31k | auto hasOptionalType() { return hasType(optionalOrAliasType()); } |
88 | | |
89 | | auto isOptionalMemberCallWithName( |
90 | | llvm::StringRef MemberName, |
91 | 2.45k | const std::optional<StatementMatcher> &Ignorable = std::nullopt) { |
92 | 2.45k | auto Exception = unless(Ignorable ? expr(anyOf(*Ignorable, cxxThisExpr()))351 |
93 | 2.45k | : cxxThisExpr()2.10k ); |
94 | 2.45k | return cxxMemberCallExpr( |
95 | 2.45k | on(expr(Exception, |
96 | 2.45k | anyOf(hasOptionalType(), |
97 | 2.45k | hasType(pointerType(pointee(optionalOrAliasType())))))), |
98 | 2.45k | callee(cxxMethodDecl(hasName(MemberName)))); |
99 | 2.45k | } |
100 | | |
101 | | auto isOptionalOperatorCallWithName( |
102 | | llvm::StringRef operator_name, |
103 | 1.40k | const std::optional<StatementMatcher> &Ignorable = std::nullopt) { |
104 | 1.40k | return cxxOperatorCallExpr( |
105 | 1.40k | hasOverloadedOperatorName(operator_name), |
106 | 1.40k | callee(cxxMethodDecl(ofClass(optionalClass()))), |
107 | 1.40k | Ignorable ? callExpr(unless(hasArgument(0, *Ignorable)))702 : callExpr()702 ); |
108 | 1.40k | } |
109 | | |
110 | 351 | auto isMakeOptionalCall() { |
111 | 351 | return callExpr( |
112 | 351 | callee(functionDecl(hasAnyName( |
113 | 351 | "std::make_optional", "base::make_optional", "absl::make_optional"))), |
114 | 351 | hasOptionalType()); |
115 | 351 | } |
116 | | |
117 | 3.51k | auto nulloptTypeDecl() { |
118 | 3.51k | return namedDecl( |
119 | 3.51k | hasAnyName("std::nullopt_t", "absl::nullopt_t", "base::nullopt_t")); |
120 | 3.51k | } |
121 | | |
122 | 2.10k | auto hasNulloptType() { return hasType(nulloptTypeDecl()); } |
123 | | |
124 | | // `optional` or `nullopt_t` |
125 | 1.40k | auto hasAnyOptionalType() { |
126 | 1.40k | return hasType(hasUnqualifiedDesugaredType( |
127 | 1.40k | recordType(hasDeclaration(anyOf(nulloptTypeDecl(), optionalClass()))))); |
128 | 1.40k | } |
129 | | |
130 | 351 | auto inPlaceClass() { |
131 | 351 | return recordDecl( |
132 | 351 | hasAnyName("std::in_place_t", "absl::in_place_t", "base::in_place_t")); |
133 | 351 | } |
134 | | |
135 | 351 | auto isOptionalNulloptConstructor() { |
136 | 351 | return cxxConstructExpr( |
137 | 351 | hasOptionalType(), |
138 | 351 | hasDeclaration(cxxConstructorDecl(parameterCountIs(1), |
139 | 351 | hasParameter(0, hasNulloptType())))); |
140 | 351 | } |
141 | | |
142 | 351 | auto isOptionalInPlaceConstructor() { |
143 | 351 | return cxxConstructExpr(hasOptionalType(), |
144 | 351 | hasArgument(0, hasType(inPlaceClass()))); |
145 | 351 | } |
146 | | |
147 | 351 | auto isOptionalValueOrConversionConstructor() { |
148 | 351 | return cxxConstructExpr( |
149 | 351 | hasOptionalType(), |
150 | 351 | unless(hasDeclaration( |
151 | 351 | cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))), |
152 | 351 | argumentCountIs(1), hasArgument(0, unless(hasNulloptType()))); |
153 | 351 | } |
154 | | |
155 | 351 | auto isOptionalValueOrConversionAssignment() { |
156 | 351 | return cxxOperatorCallExpr( |
157 | 351 | hasOverloadedOperatorName("="), |
158 | 351 | callee(cxxMethodDecl(ofClass(optionalClass()))), |
159 | 351 | unless(hasDeclaration(cxxMethodDecl( |
160 | 351 | anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))), |
161 | 351 | argumentCountIs(2), hasArgument(1, unless(hasNulloptType()))); |
162 | 351 | } |
163 | | |
164 | 351 | auto isNulloptConstructor() { |
165 | 351 | return cxxConstructExpr(hasNulloptType(), argumentCountIs(1), |
166 | 351 | hasArgument(0, hasNulloptType())); |
167 | 351 | } |
168 | | |
169 | 351 | auto isOptionalNulloptAssignment() { |
170 | 351 | return cxxOperatorCallExpr(hasOverloadedOperatorName("="), |
171 | 351 | callee(cxxMethodDecl(ofClass(optionalClass()))), |
172 | 351 | argumentCountIs(2), |
173 | 351 | hasArgument(1, hasNulloptType())); |
174 | 351 | } |
175 | | |
176 | 351 | auto isStdSwapCall() { |
177 | 351 | return callExpr(callee(functionDecl(hasName("std::swap"))), |
178 | 351 | argumentCountIs(2), hasArgument(0, hasOptionalType()), |
179 | 351 | hasArgument(1, hasOptionalType())); |
180 | 351 | } |
181 | | |
182 | 351 | auto isStdForwardCall() { |
183 | 351 | return callExpr(callee(functionDecl(hasName("std::forward"))), |
184 | 351 | argumentCountIs(1), hasArgument(0, hasOptionalType())); |
185 | 351 | } |
186 | | |
187 | | constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall"; |
188 | | |
189 | 351 | auto isValueOrStringEmptyCall() { |
190 | | // `opt.value_or("").empty()` |
191 | 351 | return cxxMemberCallExpr( |
192 | 351 | callee(cxxMethodDecl(hasName("empty"))), |
193 | 351 | onImplicitObjectArgument(ignoringImplicit( |
194 | 351 | cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), |
195 | 351 | callee(cxxMethodDecl(hasName("value_or"), |
196 | 351 | ofClass(optionalClass()))), |
197 | 351 | hasArgument(0, stringLiteral(hasSize(0)))) |
198 | 351 | .bind(ValueOrCallID)))); |
199 | 351 | } |
200 | | |
201 | 351 | auto isValueOrNotEqX() { |
202 | 1.05k | auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) { |
203 | 1.05k | return hasOperands( |
204 | 1.05k | ignoringImplicit( |
205 | 1.05k | cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))), |
206 | 1.05k | callee(cxxMethodDecl(hasName("value_or"), |
207 | 1.05k | ofClass(optionalClass()))), |
208 | 1.05k | hasArgument(0, Arg)) |
209 | 1.05k | .bind(ValueOrCallID)), |
210 | 1.05k | ignoringImplicit(Arg)); |
211 | 1.05k | }; |
212 | | |
213 | | // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd |
214 | | // support this pattern for any expression, but the AST does not have a |
215 | | // generic expression comparison facility, so we specialize to common cases |
216 | | // seen in practice. FIXME: define a matcher that compares values across |
217 | | // nodes, which would let us generalize this to any `X`. |
218 | 351 | return binaryOperation(hasOperatorName("!="), |
219 | 351 | anyOf(ComparesToSame(cxxNullPtrLiteralExpr()), |
220 | 351 | ComparesToSame(stringLiteral(hasSize(0))), |
221 | 351 | ComparesToSame(integerLiteral(equals(0))))); |
222 | 351 | } |
223 | | |
224 | 351 | auto isCallReturningOptional() { |
225 | 351 | return callExpr(hasType(qualType(anyOf( |
226 | 351 | optionalOrAliasType(), referenceType(pointee(optionalOrAliasType())))))); |
227 | 351 | } |
228 | | |
229 | | template <typename L, typename R> |
230 | 1.05k | auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) { |
231 | 1.05k | return cxxOperatorCallExpr( |
232 | 1.05k | anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")), |
233 | 1.05k | argumentCountIs(2), hasArgument(0, lhs_arg_matcher), |
234 | 1.05k | hasArgument(1, rhs_arg_matcher)); |
235 | 1.05k | } UncheckedOptionalAccessModel.cpp:auto clang::dataflow::(anonymous namespace)::isComparisonOperatorCall<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >, clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > >(clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >, clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >) Line | Count | Source | 230 | 351 | auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) { | 231 | 351 | return cxxOperatorCallExpr( | 232 | 351 | anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")), | 233 | 351 | argumentCountIs(2), hasArgument(0, lhs_arg_matcher), | 234 | 351 | hasArgument(1, rhs_arg_matcher)); | 235 | 351 | } |
UncheckedOptionalAccessModel.cpp:auto clang::dataflow::(anonymous namespace)::isComparisonOperatorCall<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >, clang::ast_matchers::internal::VariadicOperatorMatcher<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > > >(clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >, clang::ast_matchers::internal::VariadicOperatorMatcher<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > >) Line | Count | Source | 230 | 351 | auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) { | 231 | 351 | return cxxOperatorCallExpr( | 232 | 351 | anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")), | 233 | 351 | argumentCountIs(2), hasArgument(0, lhs_arg_matcher), | 234 | 351 | hasArgument(1, rhs_arg_matcher)); | 235 | 351 | } |
UncheckedOptionalAccessModel.cpp:auto clang::dataflow::(anonymous namespace)::isComparisonOperatorCall<clang::ast_matchers::internal::VariadicOperatorMatcher<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > >, clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > >(clang::ast_matchers::internal::VariadicOperatorMatcher<clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> > >, clang::ast_matchers::internal::PolymorphicMatcher<clang::ast_matchers::internal::matcher_hasType0Matcher, void (clang::ast_matchers::internal::TypeList<clang::Expr, clang::FriendDecl, clang::TypedefNameDecl, clang::ValueDecl, clang::CXXBaseSpecifier>), clang::ast_matchers::internal::Matcher<clang::QualType> >) Line | Count | Source | 230 | 351 | auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) { | 231 | 351 | return cxxOperatorCallExpr( | 232 | 351 | anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")), | 233 | 351 | argumentCountIs(2), hasArgument(0, lhs_arg_matcher), | 234 | 351 | hasArgument(1, rhs_arg_matcher)); | 235 | 351 | } |
|
236 | | |
237 | | /// Ensures that `Expr` is mapped to a `BoolValue` and returns it. |
238 | 138 | BoolValue &forceBoolValue(Environment &Env, const Expr &Expr) { |
239 | 138 | auto *Value = cast_or_null<BoolValue>(Env.getValue(Expr, SkipPast::None)); |
240 | 138 | if (Value != nullptr) |
241 | 18 | return *Value; |
242 | | |
243 | 120 | auto &Loc = Env.createStorageLocation(Expr); |
244 | 120 | Value = &Env.makeAtomicBoolValue(); |
245 | 120 | Env.setValue(Loc, *Value); |
246 | 120 | Env.setStorageLocation(Expr, Loc); |
247 | 120 | return *Value; |
248 | 138 | } |
249 | | |
250 | | /// Sets `HasValueVal` as the symbolic value that represents the "has_value" |
251 | | /// property of the optional value `OptionalVal`. |
252 | 1.40k | void setHasValue(Value &OptionalVal, BoolValue &HasValueVal) { |
253 | 1.40k | OptionalVal.setProperty("has_value", HasValueVal); |
254 | 1.40k | } |
255 | | |
256 | | /// Creates a symbolic value for an `optional` value using `HasValueVal` as the |
257 | | /// symbolic value of its "has_value" property. |
258 | 1.05k | StructValue &createOptionalValue(Environment &Env, BoolValue &HasValueVal) { |
259 | 1.05k | auto &OptionalVal = Env.create<StructValue>(); |
260 | 1.05k | setHasValue(OptionalVal, HasValueVal); |
261 | 1.05k | return OptionalVal; |
262 | 1.05k | } |
263 | | |
264 | | /// Returns the symbolic value that represents the "has_value" property of the |
265 | | /// optional value `OptionalVal`. Returns null if `OptionalVal` is null. |
266 | 504 | BoolValue *getHasValue(Environment &Env, Value *OptionalVal) { |
267 | 504 | if (OptionalVal != nullptr) { |
268 | 504 | auto *HasValueVal = |
269 | 504 | cast_or_null<BoolValue>(OptionalVal->getProperty("has_value")); |
270 | 504 | if (HasValueVal == nullptr) { |
271 | 24 | HasValueVal = &Env.makeAtomicBoolValue(); |
272 | 24 | OptionalVal->setProperty("has_value", *HasValueVal); |
273 | 24 | } |
274 | 504 | return HasValueVal; |
275 | 504 | } |
276 | 0 | return nullptr; |
277 | 504 | } |
278 | | |
279 | | /// Returns true if and only if `Type` is an optional type. |
280 | 1.14k | bool isOptionalType(QualType Type) { |
281 | 1.14k | if (!Type->isRecordType()) |
282 | 624 | return false; |
283 | 517 | const CXXRecordDecl *D = Type->getAsCXXRecordDecl(); |
284 | 517 | return D != nullptr && hasOptionalClassName(*D); |
285 | 1.14k | } |
286 | | |
287 | | /// Returns the number of optional wrappers in `Type`. |
288 | | /// |
289 | | /// For example, if `Type` is `optional<optional<int>>`, the result of this |
290 | | /// function will be 2. |
291 | 732 | int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) { |
292 | 732 | if (!isOptionalType(Type)) |
293 | 672 | return 0; |
294 | 60 | return 1 + countOptionalWrappers( |
295 | 60 | ASTCtx, |
296 | 60 | cast<ClassTemplateSpecializationDecl>(Type->getAsRecordDecl()) |
297 | 60 | ->getTemplateArgs() |
298 | 60 | .get(0) |
299 | 60 | .getAsType() |
300 | 60 | .getDesugaredType(ASTCtx)); |
301 | 732 | } |
302 | | |
303 | | /// Tries to initialize the `optional`'s value (that is, contents), and return |
304 | | /// its location. Returns nullptr if the value can't be represented. |
305 | | StorageLocation *maybeInitializeOptionalValueMember(QualType Q, |
306 | | Value &OptionalVal, |
307 | 963 | Environment &Env) { |
308 | | // The "value" property represents a synthetic field. As such, it needs |
309 | | // `StorageLocation`, like normal fields (and other variables). So, we model |
310 | | // it with a `ReferenceValue`, since that includes a storage location. Once |
311 | | // the property is set, it will be shared by all environments that access the |
312 | | // `Value` representing the optional (here, `OptionalVal`). |
313 | 963 | if (auto *ValueProp = OptionalVal.getProperty("value")) { |
314 | 357 | auto *ValueRef = clang::cast<ReferenceValue>(ValueProp); |
315 | 357 | auto &ValueLoc = ValueRef->getReferentLoc(); |
316 | 357 | if (Env.getValue(ValueLoc) == nullptr) { |
317 | | // The property was previously set, but the value has been lost. This can |
318 | | // happen, for example, because of an environment merge (where the two |
319 | | // environments mapped the property to different values, which resulted in |
320 | | // them both being discarded), or when two blocks in the CFG, with neither |
321 | | // a dominator of the other, visit the same optional value, or even when a |
322 | | // block is revisited during testing to collect per-statement state. |
323 | | // FIXME: This situation means that the optional contents are not shared |
324 | | // between branches and the like. Practically, this lack of sharing |
325 | | // reduces the precision of the model when the contents are relevant to |
326 | | // the check, like another optional or a boolean that influences control |
327 | | // flow. |
328 | 315 | auto *ValueVal = Env.createValue(ValueLoc.getType()); |
329 | 315 | if (ValueVal == nullptr) |
330 | 0 | return nullptr; |
331 | 315 | Env.setValue(ValueLoc, *ValueVal); |
332 | 315 | } |
333 | 357 | return &ValueLoc; |
334 | 357 | } |
335 | | |
336 | 606 | auto Ty = Q.getNonReferenceType(); |
337 | 606 | auto *ValueVal = Env.createValue(Ty); |
338 | 606 | if (ValueVal == nullptr) |
339 | 0 | return nullptr; |
340 | 606 | auto &ValueLoc = Env.createStorageLocation(Ty); |
341 | 606 | Env.setValue(ValueLoc, *ValueVal); |
342 | 606 | auto &ValueRef = Env.create<ReferenceValue>(ValueLoc); |
343 | 606 | OptionalVal.setProperty("value", ValueRef); |
344 | 606 | return &ValueLoc; |
345 | 606 | } |
346 | | |
347 | | void initializeOptionalReference(const Expr *OptionalExpr, |
348 | | const MatchFinder::MatchResult &, |
349 | 1.71k | LatticeTransferState &State) { |
350 | 1.71k | if (auto *OptionalVal = |
351 | 1.71k | State.Env.getValue(*OptionalExpr, SkipPast::Reference)) { |
352 | 1.65k | if (OptionalVal->getProperty("has_value") == nullptr) { |
353 | 201 | setHasValue(*OptionalVal, State.Env.makeAtomicBoolValue()); |
354 | 201 | } |
355 | 1.65k | } |
356 | 1.71k | } |
357 | | |
358 | | /// Returns true if and only if `OptionalVal` is initialized and known to be |
359 | | /// empty in `Env`. |
360 | 127 | bool isEmptyOptional(const Value &OptionalVal, const Environment &Env) { |
361 | 127 | auto *HasValueVal = |
362 | 127 | cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); |
363 | 127 | return HasValueVal != nullptr && |
364 | 127 | Env.flowConditionImplies(Env.makeNot(*HasValueVal)); |
365 | 127 | } |
366 | | |
367 | | /// Returns true if and only if `OptionalVal` is initialized and known to be |
368 | | /// non-empty in `Env`. |
369 | 478 | bool isNonEmptyOptional(const Value &OptionalVal, const Environment &Env) { |
370 | 478 | auto *HasValueVal = |
371 | 478 | cast_or_null<BoolValue>(OptionalVal.getProperty("has_value")); |
372 | 478 | return HasValueVal != nullptr && Env.flowConditionImplies(*HasValueVal); |
373 | 478 | } |
374 | | |
375 | | StorageLocation *maybeSkipPointer(StorageLocation *Loc, |
376 | 693 | const Environment &Env) { |
377 | 693 | if (Loc == nullptr) |
378 | 6 | return nullptr; |
379 | 687 | if (auto *Val = dyn_cast_or_null<PointerValue>(Env.getValue(*Loc))) |
380 | 12 | return &Val->getPointeeLoc(); |
381 | 675 | return Loc; |
382 | 687 | } |
383 | | |
384 | 1.73k | Value *getValueBehindPossiblePointer(const Expr &E, const Environment &Env) { |
385 | 1.73k | Value *Val = Env.getValue(E, SkipPast::Reference); |
386 | 1.73k | if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Val)) |
387 | 90 | return Env.getValue(PointerVal->getPointeeLoc()); |
388 | 1.64k | return Val; |
389 | 1.73k | } |
390 | | |
391 | | void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, |
392 | 921 | LatticeTransferState &State) { |
393 | 921 | if (auto *OptionalVal = |
394 | 921 | getValueBehindPossiblePointer(*ObjectExpr, State.Env)) { |
395 | 891 | if (State.Env.getStorageLocation(*UnwrapExpr, SkipPast::None) == nullptr) |
396 | 891 | if (auto *Loc = maybeInitializeOptionalValueMember( |
397 | 891 | UnwrapExpr->getType(), *OptionalVal, State.Env)) |
398 | 891 | State.Env.setStorageLocation(*UnwrapExpr, *Loc); |
399 | 891 | } |
400 | 921 | } |
401 | | |
402 | | void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, |
403 | 72 | LatticeTransferState &State) { |
404 | 72 | if (auto *OptionalVal = |
405 | 72 | getValueBehindPossiblePointer(*ObjectExpr, State.Env)) { |
406 | 72 | if (auto *Loc = maybeInitializeOptionalValueMember( |
407 | 72 | UnwrapExpr->getType()->getPointeeType(), *OptionalVal, State.Env)) { |
408 | 72 | State.Env.setValueStrict(*UnwrapExpr, |
409 | 72 | State.Env.create<PointerValue>(*Loc)); |
410 | 72 | } |
411 | 72 | } |
412 | 72 | } |
413 | | |
414 | | void transferMakeOptionalCall(const CallExpr *E, |
415 | | const MatchFinder::MatchResult &, |
416 | 24 | LatticeTransferState &State) { |
417 | 24 | auto &Loc = State.Env.createStorageLocation(*E); |
418 | 24 | State.Env.setStorageLocation(*E, Loc); |
419 | 24 | State.Env.setValue( |
420 | 24 | Loc, createOptionalValue(State.Env, State.Env.getBoolLiteralValue(true))); |
421 | 24 | } |
422 | | |
423 | | void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr, |
424 | | const MatchFinder::MatchResult &, |
425 | 234 | LatticeTransferState &State) { |
426 | 234 | if (auto *HasValueVal = getHasValue( |
427 | 234 | State.Env, getValueBehindPossiblePointer( |
428 | 234 | *CallExpr->getImplicitObjectArgument(), State.Env))) { |
429 | 234 | auto &CallExprLoc = State.Env.createStorageLocation(*CallExpr); |
430 | 234 | State.Env.setValue(CallExprLoc, *HasValueVal); |
431 | 234 | State.Env.setStorageLocation(*CallExpr, CallExprLoc); |
432 | 234 | } |
433 | 234 | } |
434 | | |
435 | | /// `ModelPred` builds a logical formula relating the predicate in |
436 | | /// `ValueOrPredExpr` to the optional's `has_value` property. |
437 | | void transferValueOrImpl(const clang::Expr *ValueOrPredExpr, |
438 | | const MatchFinder::MatchResult &Result, |
439 | | LatticeTransferState &State, |
440 | | BoolValue &(*ModelPred)(Environment &Env, |
441 | | BoolValue &ExprVal, |
442 | 30 | BoolValue &HasValueVal)) { |
443 | 30 | auto &Env = State.Env; |
444 | | |
445 | 30 | const auto *ObjectArgumentExpr = |
446 | 30 | Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID) |
447 | 30 | ->getImplicitObjectArgument(); |
448 | | |
449 | 30 | auto *HasValueVal = getHasValue( |
450 | 30 | State.Env, getValueBehindPossiblePointer(*ObjectArgumentExpr, State.Env)); |
451 | 30 | if (HasValueVal == nullptr) |
452 | 0 | return; |
453 | | |
454 | 30 | Env.addToFlowCondition( |
455 | 30 | ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr), *HasValueVal)); |
456 | 30 | } |
457 | | |
458 | | void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr, |
459 | | const MatchFinder::MatchResult &Result, |
460 | 6 | LatticeTransferState &State) { |
461 | 6 | return transferValueOrImpl(ComparisonExpr, Result, State, |
462 | 6 | [](Environment &Env, BoolValue &ExprVal, |
463 | 6 | BoolValue &HasValueVal) -> BoolValue & { |
464 | | // If the result is *not* empty, then we know the |
465 | | // optional must have been holding a value. If |
466 | | // `ExprVal` is true, though, we don't learn |
467 | | // anything definite about `has_value`, so we |
468 | | // don't add any corresponding implications to |
469 | | // the flow condition. |
470 | 6 | return Env.makeImplication(Env.makeNot(ExprVal), |
471 | 6 | HasValueVal); |
472 | 6 | }); |
473 | 6 | } |
474 | | |
475 | | void transferValueOrNotEqX(const Expr *ComparisonExpr, |
476 | | const MatchFinder::MatchResult &Result, |
477 | 24 | LatticeTransferState &State) { |
478 | 24 | transferValueOrImpl(ComparisonExpr, Result, State, |
479 | 24 | [](Environment &Env, BoolValue &ExprVal, |
480 | 24 | BoolValue &HasValueVal) -> BoolValue & { |
481 | | // We know that if `(opt.value_or(X) != X)` then |
482 | | // `opt.hasValue()`, even without knowing further |
483 | | // details about the contents of `opt`. |
484 | 24 | return Env.makeImplication(ExprVal, HasValueVal); |
485 | 24 | }); |
486 | 24 | } |
487 | | |
488 | | void transferCallReturningOptional(const CallExpr *E, |
489 | | const MatchFinder::MatchResult &Result, |
490 | 450 | LatticeTransferState &State) { |
491 | 450 | if (State.Env.getStorageLocation(*E, SkipPast::None) != nullptr) |
492 | 189 | return; |
493 | | |
494 | 261 | auto &Loc = State.Env.createStorageLocation(*E); |
495 | 261 | State.Env.setStorageLocation(*E, Loc); |
496 | 261 | State.Env.setValue( |
497 | 261 | Loc, createOptionalValue(State.Env, State.Env.makeAtomicBoolValue())); |
498 | 261 | } |
499 | | |
500 | | void assignOptionalValue(const Expr &E, Environment &Env, |
501 | 645 | BoolValue &HasValueVal) { |
502 | 645 | if (auto *OptionalLoc = maybeSkipPointer( |
503 | 645 | Env.getStorageLocation(E, SkipPast::Reference), Env)) { |
504 | 645 | Env.setValue(*OptionalLoc, createOptionalValue(Env, HasValueVal)); |
505 | 645 | } |
506 | 645 | } |
507 | | |
508 | | /// Returns a symbolic value for the "has_value" property of an `optional<T>` |
509 | | /// value that is constructed/assigned from a value of type `U` or `optional<U>` |
510 | | /// where `T` is constructible from `U`. |
511 | | BoolValue &valueOrConversionHasValue(const FunctionDecl &F, const Expr &E, |
512 | | const MatchFinder::MatchResult &MatchRes, |
513 | 336 | LatticeTransferState &State) { |
514 | 336 | assert(F.getTemplateSpecializationArgs() != nullptr); |
515 | 336 | assert(F.getTemplateSpecializationArgs()->size() > 0); |
516 | | |
517 | 336 | const int TemplateParamOptionalWrappersCount = |
518 | 336 | countOptionalWrappers(*MatchRes.Context, F.getTemplateSpecializationArgs() |
519 | 336 | ->get(0) |
520 | 336 | .getAsType() |
521 | 336 | .getNonReferenceType()); |
522 | 336 | const int ArgTypeOptionalWrappersCount = countOptionalWrappers( |
523 | 336 | *MatchRes.Context, E.getType().getNonReferenceType()); |
524 | | |
525 | | // Check if this is a constructor/assignment call for `optional<T>` with |
526 | | // argument of type `U` such that `T` is constructible from `U`. |
527 | 336 | if (TemplateParamOptionalWrappersCount == ArgTypeOptionalWrappersCount) |
528 | 288 | return State.Env.getBoolLiteralValue(true); |
529 | | |
530 | | // This is a constructor/assignment call for `optional<T>` with argument of |
531 | | // type `optional<U>` such that `T` is constructible from `U`. |
532 | 48 | if (auto *HasValueVal = |
533 | 48 | getHasValue(State.Env, State.Env.getValue(E, SkipPast::Reference))) |
534 | 48 | return *HasValueVal; |
535 | 0 | return State.Env.makeAtomicBoolValue(); |
536 | 48 | } |
537 | | |
538 | | void transferValueOrConversionConstructor( |
539 | | const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes, |
540 | 300 | LatticeTransferState &State) { |
541 | 300 | assert(E->getNumArgs() > 0); |
542 | | |
543 | 300 | assignOptionalValue(*E, State.Env, |
544 | 300 | valueOrConversionHasValue(*E->getConstructor(), |
545 | 300 | *E->getArg(0), MatchRes, |
546 | 300 | State)); |
547 | 300 | } |
548 | | |
549 | | void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal, |
550 | 69 | LatticeTransferState &State) { |
551 | 69 | assert(E->getNumArgs() > 0); |
552 | | |
553 | 69 | auto *OptionalLoc = |
554 | 69 | State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); |
555 | 69 | if (OptionalLoc == nullptr) |
556 | 0 | return; |
557 | | |
558 | 69 | State.Env.setValue(*OptionalLoc, createOptionalValue(State.Env, HasValueVal)); |
559 | | |
560 | | // Assign a storage location for the whole expression. |
561 | 69 | State.Env.setStorageLocation(*E, *OptionalLoc); |
562 | 69 | } |
563 | | |
564 | | void transferValueOrConversionAssignment( |
565 | | const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes, |
566 | 36 | LatticeTransferState &State) { |
567 | 36 | assert(E->getNumArgs() > 1); |
568 | 36 | transferAssignment(E, |
569 | 36 | valueOrConversionHasValue(*E->getDirectCallee(), |
570 | 36 | *E->getArg(1), MatchRes, State), |
571 | 36 | State); |
572 | 36 | } |
573 | | |
574 | | void transferNulloptAssignment(const CXXOperatorCallExpr *E, |
575 | | const MatchFinder::MatchResult &, |
576 | 33 | LatticeTransferState &State) { |
577 | 33 | transferAssignment(E, State.Env.getBoolLiteralValue(false), State); |
578 | 33 | } |
579 | | |
580 | | void transferSwap(StorageLocation *Loc1, StorageLocation *Loc2, |
581 | 60 | Environment &Env) { |
582 | | // We account for cases where one or both of the optionals are not modeled, |
583 | | // either lacking associated storage locations, or lacking values associated |
584 | | // to such storage locations. |
585 | | |
586 | 60 | if (Loc1 == nullptr) { |
587 | 6 | if (Loc2 != nullptr) |
588 | 6 | Env.setValue(*Loc2, createOptionalValue(Env, Env.makeAtomicBoolValue())); |
589 | 6 | return; |
590 | 6 | } |
591 | 54 | if (Loc2 == nullptr) { |
592 | 6 | Env.setValue(*Loc1, createOptionalValue(Env, Env.makeAtomicBoolValue())); |
593 | 6 | return; |
594 | 6 | } |
595 | | |
596 | | // Both expressions have locations, though they may not have corresponding |
597 | | // values. In that case, we create a fresh value at this point. Note that if |
598 | | // two branches both do this, they will not share the value, but it at least |
599 | | // allows for local reasoning about the value. To avoid the above, we would |
600 | | // need *lazy* value allocation. |
601 | | // FIXME: allocate values lazily, instead of just creating a fresh value. |
602 | 48 | auto *Val1 = Env.getValue(*Loc1); |
603 | 48 | if (Val1 == nullptr) |
604 | 12 | Val1 = &createOptionalValue(Env, Env.makeAtomicBoolValue()); |
605 | | |
606 | 48 | auto *Val2 = Env.getValue(*Loc2); |
607 | 48 | if (Val2 == nullptr) |
608 | 12 | Val2 = &createOptionalValue(Env, Env.makeAtomicBoolValue()); |
609 | | |
610 | 48 | Env.setValue(*Loc1, *Val2); |
611 | 48 | Env.setValue(*Loc2, *Val1); |
612 | 48 | } |
613 | | |
614 | | void transferSwapCall(const CXXMemberCallExpr *E, |
615 | | const MatchFinder::MatchResult &, |
616 | 48 | LatticeTransferState &State) { |
617 | 48 | assert(E->getNumArgs() == 1); |
618 | 48 | transferSwap(maybeSkipPointer( |
619 | 48 | State.Env.getStorageLocation(*E->getImplicitObjectArgument(), |
620 | 48 | SkipPast::Reference), |
621 | 48 | State.Env), |
622 | 48 | State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference), |
623 | 48 | State.Env); |
624 | 48 | } |
625 | | |
626 | | void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &, |
627 | 12 | LatticeTransferState &State) { |
628 | 12 | assert(E->getNumArgs() == 2); |
629 | 12 | transferSwap(State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference), |
630 | 12 | State.Env.getStorageLocation(*E->getArg(1), SkipPast::Reference), |
631 | 12 | State.Env); |
632 | 12 | } |
633 | | |
634 | | void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &, |
635 | 0 | LatticeTransferState &State) { |
636 | 0 | assert(E->getNumArgs() == 1); |
637 | | |
638 | 0 | StorageLocation *LocRet = State.Env.getStorageLocation(*E, SkipPast::None); |
639 | 0 | if (LocRet != nullptr) |
640 | 0 | return; |
641 | | |
642 | 0 | StorageLocation *LocArg = |
643 | 0 | State.Env.getStorageLocation(*E->getArg(0), SkipPast::Reference); |
644 | |
|
645 | 0 | if (LocArg == nullptr) |
646 | 0 | return; |
647 | | |
648 | 0 | Value *ValArg = State.Env.getValue(*LocArg); |
649 | 0 | if (ValArg == nullptr) |
650 | 0 | ValArg = &createOptionalValue(State.Env, State.Env.makeAtomicBoolValue()); |
651 | | |
652 | | // Create a new storage location |
653 | 0 | LocRet = &State.Env.createStorageLocation(*E); |
654 | 0 | State.Env.setStorageLocation(*E, *LocRet); |
655 | |
|
656 | 0 | State.Env.setValue(*LocRet, *ValArg); |
657 | 0 | } |
658 | | |
659 | | BoolValue &evaluateEquality(Environment &Env, BoolValue &EqVal, BoolValue &LHS, |
660 | 108 | BoolValue &RHS) { |
661 | | // Logically, an optional<T> object is composed of two values - a `has_value` |
662 | | // bit and a value of type T. Equality of optional objects compares both |
663 | | // values. Therefore, merely comparing the `has_value` bits isn't sufficient: |
664 | | // when two optional objects are engaged, the equality of their respective |
665 | | // values of type T matters. Since we only track the `has_value` bits, we |
666 | | // can't make any conclusions about equality when we know that two optional |
667 | | // objects are engaged. |
668 | | // |
669 | | // We express this as two facts about the equality: |
670 | | // a) EqVal => (LHS & RHS) v (!RHS & !LHS) |
671 | | // If they are equal, then either both are set or both are unset. |
672 | | // b) (!LHS & !RHS) => EqVal |
673 | | // If neither is set, then they are equal. |
674 | | // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula. |
675 | 108 | return Env.makeAnd( |
676 | 108 | Env.makeImplication( |
677 | 108 | EqVal, Env.makeOr(Env.makeAnd(LHS, RHS), |
678 | 108 | Env.makeAnd(Env.makeNot(LHS), Env.makeNot(RHS)))), |
679 | 108 | Env.makeImplication(Env.makeNot(EqVal), Env.makeOr(LHS, RHS))); |
680 | 108 | } |
681 | | |
682 | | void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr, |
683 | | const MatchFinder::MatchResult &, |
684 | 84 | LatticeTransferState &State) { |
685 | 84 | Environment &Env = State.Env; |
686 | 84 | auto *CmpValue = &forceBoolValue(Env, *CmpExpr); |
687 | 84 | if (auto *LHasVal = getHasValue( |
688 | 84 | Env, Env.getValue(*CmpExpr->getArg(0), SkipPast::Reference))) |
689 | 84 | if (auto *RHasVal = getHasValue( |
690 | 84 | Env, Env.getValue(*CmpExpr->getArg(1), SkipPast::Reference))) { |
691 | 84 | if (CmpExpr->getOperator() == clang::OO_ExclaimEqual) |
692 | 42 | CmpValue = &State.Env.makeNot(*CmpValue); |
693 | 84 | Env.addToFlowCondition( |
694 | 84 | evaluateEquality(Env, *CmpValue, *LHasVal, *RHasVal)); |
695 | 84 | } |
696 | 84 | } |
697 | | |
698 | | void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr, |
699 | 24 | const clang::Expr *E, Environment &Env) { |
700 | 24 | auto *CmpValue = &forceBoolValue(Env, *CmpExpr); |
701 | 24 | if (auto *HasVal = getHasValue(Env, Env.getValue(*E, SkipPast::Reference))) { |
702 | 24 | if (CmpExpr->getOperator() == clang::OO_ExclaimEqual) |
703 | 12 | CmpValue = &Env.makeNot(*CmpValue); |
704 | 24 | Env.addToFlowCondition(evaluateEquality(Env, *CmpValue, *HasVal, |
705 | 24 | Env.getBoolLiteralValue(true))); |
706 | 24 | } |
707 | 24 | } |
708 | | |
709 | | std::optional<StatementMatcher> |
710 | 351 | ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { |
711 | 351 | if (Options.IgnoreSmartPointerDereference) { |
712 | 351 | auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr( |
713 | 351 | anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")), |
714 | 351 | unless(hasArgument(0, expr(hasOptionalType())))))); |
715 | 351 | return expr( |
716 | 351 | anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse)))); |
717 | 351 | } |
718 | 0 | return std::nullopt; |
719 | 351 | } |
720 | | |
721 | | StatementMatcher |
722 | 702 | valueCall(const std::optional<StatementMatcher> &IgnorableOptional) { |
723 | 702 | return isOptionalMemberCallWithName("value", IgnorableOptional); |
724 | 702 | } |
725 | | |
726 | | StatementMatcher |
727 | 351 | valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) { |
728 | 351 | return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional), |
729 | 351 | isOptionalOperatorCallWithName("->", IgnorableOptional))); |
730 | 351 | } |
731 | | |
732 | 351 | auto buildTransferMatchSwitch() { |
733 | | // FIXME: Evaluate the efficiency of matchers. If using matchers results in a |
734 | | // lot of duplicated work (e.g. string comparisons), consider providing APIs |
735 | | // that avoid it through memoization. |
736 | 351 | return CFGMatchSwitchBuilder<LatticeTransferState>() |
737 | | // Attach a symbolic "has_value" state to optional values that we see for |
738 | | // the first time. |
739 | 351 | .CaseOfCFGStmt<Expr>( |
740 | 351 | expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()), |
741 | 351 | initializeOptionalReference) |
742 | | |
743 | | // make_optional |
744 | 351 | .CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall) |
745 | | |
746 | | // optional::optional (in place) |
747 | 351 | .CaseOfCFGStmt<CXXConstructExpr>( |
748 | 351 | isOptionalInPlaceConstructor(), |
749 | 351 | [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, |
750 | 351 | LatticeTransferState &State) { |
751 | 24 | assignOptionalValue(*E, State.Env, |
752 | 24 | State.Env.getBoolLiteralValue(true)); |
753 | 24 | }) |
754 | | // nullopt_t::nullopt_t |
755 | 351 | .CaseOfCFGStmt<CXXConstructExpr>( |
756 | 351 | isNulloptConstructor(), |
757 | 351 | [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, |
758 | 351 | LatticeTransferState &State) { |
759 | 174 | assignOptionalValue(*E, State.Env, |
760 | 174 | State.Env.getBoolLiteralValue(false)); |
761 | 174 | }) |
762 | | // optional::optional(nullopt_t) |
763 | 351 | .CaseOfCFGStmt<CXXConstructExpr>( |
764 | 351 | isOptionalNulloptConstructor(), |
765 | 351 | [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, |
766 | 351 | LatticeTransferState &State) { |
767 | 117 | assignOptionalValue(*E, State.Env, |
768 | 117 | State.Env.getBoolLiteralValue(false)); |
769 | 117 | }) |
770 | | // optional::optional (value/conversion) |
771 | 351 | .CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(), |
772 | 351 | transferValueOrConversionConstructor) |
773 | | |
774 | | // optional::operator= |
775 | 351 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
776 | 351 | isOptionalValueOrConversionAssignment(), |
777 | 351 | transferValueOrConversionAssignment) |
778 | 351 | .CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(), |
779 | 351 | transferNulloptAssignment) |
780 | | |
781 | | // optional::value |
782 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
783 | 351 | valueCall(std::nullopt), |
784 | 351 | [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
785 | 867 | LatticeTransferState &State) { |
786 | 867 | transferUnwrapCall(E, E->getImplicitObjectArgument(), State); |
787 | 867 | }) |
788 | | |
789 | | // optional::operator* |
790 | 351 | .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"), |
791 | 351 | [](const CallExpr *E, |
792 | 351 | const MatchFinder::MatchResult &, |
793 | 351 | LatticeTransferState &State) { |
794 | 54 | transferUnwrapCall(E, E->getArg(0), State); |
795 | 54 | }) |
796 | | |
797 | | // optional::operator-> |
798 | 351 | .CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"), |
799 | 351 | [](const CallExpr *E, |
800 | 351 | const MatchFinder::MatchResult &, |
801 | 351 | LatticeTransferState &State) { |
802 | 72 | transferArrowOpCall(E, E->getArg(0), State); |
803 | 72 | }) |
804 | | |
805 | | // optional::has_value |
806 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
807 | 351 | isOptionalMemberCallWithName("has_value"), |
808 | 351 | transferOptionalHasValueCall) |
809 | | |
810 | | // optional::operator bool |
811 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
812 | 351 | isOptionalMemberCallWithName("operator bool"), |
813 | 351 | transferOptionalHasValueCall) |
814 | | |
815 | | // optional::emplace |
816 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
817 | 351 | isOptionalMemberCallWithName("emplace"), |
818 | 351 | [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
819 | 351 | LatticeTransferState &State) { |
820 | 12 | assignOptionalValue(*E->getImplicitObjectArgument(), State.Env, |
821 | 12 | State.Env.getBoolLiteralValue(true)); |
822 | 12 | }) |
823 | | |
824 | | // optional::reset |
825 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
826 | 351 | isOptionalMemberCallWithName("reset"), |
827 | 351 | [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
828 | 351 | LatticeTransferState &State) { |
829 | 18 | assignOptionalValue(*E->getImplicitObjectArgument(), State.Env, |
830 | 18 | State.Env.getBoolLiteralValue(false)); |
831 | 18 | }) |
832 | | |
833 | | // optional::swap |
834 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>(isOptionalMemberCallWithName("swap"), |
835 | 351 | transferSwapCall) |
836 | | |
837 | | // std::swap |
838 | 351 | .CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall) |
839 | | |
840 | | // std::forward |
841 | 351 | .CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall) |
842 | | |
843 | | // opt.value_or("").empty() |
844 | 351 | .CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(), |
845 | 351 | transferValueOrStringEmptyCall) |
846 | | |
847 | | // opt.value_or(X) != X |
848 | 351 | .CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX) |
849 | | |
850 | | // Comparisons (==, !=): |
851 | 351 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
852 | 351 | isComparisonOperatorCall(hasAnyOptionalType(), hasAnyOptionalType()), |
853 | 351 | transferOptionalAndOptionalCmp) |
854 | 351 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
855 | 351 | isComparisonOperatorCall(hasOptionalType(), |
856 | 351 | unless(hasAnyOptionalType())), |
857 | 351 | [](const clang::CXXOperatorCallExpr *Cmp, |
858 | 351 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
859 | 12 | transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env); |
860 | 12 | }) |
861 | 351 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
862 | 351 | isComparisonOperatorCall(unless(hasAnyOptionalType()), |
863 | 351 | hasOptionalType()), |
864 | 351 | [](const clang::CXXOperatorCallExpr *Cmp, |
865 | 351 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
866 | 12 | transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env); |
867 | 12 | }) |
868 | | |
869 | | // returns optional |
870 | 351 | .CaseOfCFGStmt<CallExpr>(isCallReturningOptional(), |
871 | 351 | transferCallReturningOptional) |
872 | | |
873 | 351 | .Build(); |
874 | 351 | } |
875 | | |
876 | | std::vector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr, |
877 | 477 | const Environment &Env) { |
878 | 477 | if (auto *OptionalVal = getValueBehindPossiblePointer(*ObjectExpr, Env)) { |
879 | 471 | auto *Prop = OptionalVal->getProperty("has_value"); |
880 | 471 | if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) { |
881 | 471 | if (Env.flowConditionImplies(*HasValueVal)) |
882 | 261 | return {}; |
883 | 471 | } |
884 | 471 | } |
885 | | |
886 | | // Record that this unwrap is *not* provably safe. |
887 | | // FIXME: include either the name of the optional (if applicable) or a source |
888 | | // range of the access for easier interpretation of the result. |
889 | 216 | return {ObjectExpr->getBeginLoc()}; |
890 | 477 | } |
891 | | |
892 | | auto buildDiagnoseMatchSwitch( |
893 | 351 | const UncheckedOptionalAccessModelOptions &Options) { |
894 | | // FIXME: Evaluate the efficiency of matchers. If using matchers results in a |
895 | | // lot of duplicated work (e.g. string comparisons), consider providing APIs |
896 | | // that avoid it through memoization. |
897 | 351 | auto IgnorableOptional = ignorableOptional(Options); |
898 | 351 | return CFGMatchSwitchBuilder<const Environment, std::vector<SourceLocation>>() |
899 | | // optional::value |
900 | 351 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
901 | 351 | valueCall(IgnorableOptional), |
902 | 351 | [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
903 | 420 | const Environment &Env) { |
904 | 420 | return diagnoseUnwrapCall(E->getImplicitObjectArgument(), Env); |
905 | 420 | }) |
906 | | |
907 | | // optional::operator*, optional::operator-> |
908 | 351 | .CaseOfCFGStmt<CallExpr>(valueOperatorCall(IgnorableOptional), |
909 | 351 | [](const CallExpr *E, |
910 | 351 | const MatchFinder::MatchResult &, |
911 | 351 | const Environment &Env) { |
912 | 57 | return diagnoseUnwrapCall(E->getArg(0), Env); |
913 | 57 | }) |
914 | 351 | .Build(); |
915 | 351 | } |
916 | | |
917 | | } // namespace |
918 | | |
919 | | ast_matchers::DeclarationMatcher |
920 | 0 | UncheckedOptionalAccessModel::optionalClassDecl() { |
921 | 0 | return optionalClass(); |
922 | 0 | } |
923 | | |
924 | | UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx) |
925 | | : DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx), |
926 | 351 | TransferMatchSwitch(buildTransferMatchSwitch()) {} |
927 | | |
928 | | void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt, |
929 | 11.8k | NoopLattice &L, Environment &Env) { |
930 | 11.8k | LatticeTransferState State(L, Env); |
931 | 11.8k | TransferMatchSwitch(Elt, getASTContext(), State); |
932 | 11.8k | } |
933 | | |
934 | | ComparisonResult UncheckedOptionalAccessModel::compare( |
935 | | QualType Type, const Value &Val1, const Environment &Env1, |
936 | 115 | const Value &Val2, const Environment &Env2) { |
937 | 115 | if (!isOptionalType(Type)) |
938 | 32 | return ComparisonResult::Unknown; |
939 | 83 | bool MustNonEmpty1 = isNonEmptyOptional(Val1, Env1); |
940 | 83 | bool MustNonEmpty2 = isNonEmptyOptional(Val2, Env2); |
941 | 83 | if (MustNonEmpty1 && MustNonEmpty244 ) |
942 | 30 | return ComparisonResult::Same; |
943 | | // If exactly one is true, then they're different, no reason to check whether |
944 | | // they're definitely empty. |
945 | 53 | if (MustNonEmpty1 || MustNonEmpty239 ) |
946 | 14 | return ComparisonResult::Different; |
947 | | // Check if they're both definitely empty. |
948 | 39 | return (isEmptyOptional(Val1, Env1) && isEmptyOptional(Val2, Env2)18 ) |
949 | 39 | ? ComparisonResult::Same11 |
950 | 39 | : ComparisonResult::Different28 ; |
951 | 53 | } |
952 | | |
953 | | bool UncheckedOptionalAccessModel::merge(QualType Type, const Value &Val1, |
954 | | const Environment &Env1, |
955 | | const Value &Val2, |
956 | | const Environment &Env2, |
957 | | Value &MergedVal, |
958 | 294 | Environment &MergedEnv) { |
959 | 294 | if (!isOptionalType(Type)) |
960 | 138 | return true; |
961 | | // FIXME: uses same approach as join for `BoolValues`. Requires non-const |
962 | | // values, though, so will require updating the interface. |
963 | 156 | auto &HasValueVal = MergedEnv.makeAtomicBoolValue(); |
964 | 156 | bool MustNonEmpty1 = isNonEmptyOptional(Val1, Env1); |
965 | 156 | bool MustNonEmpty2 = isNonEmptyOptional(Val2, Env2); |
966 | 156 | if (MustNonEmpty1 && MustNonEmpty262 ) |
967 | 33 | MergedEnv.addToFlowCondition(HasValueVal); |
968 | 123 | else if ( |
969 | | // Only make the costly calls to `isEmptyOptional` if we got "unknown" |
970 | | // (false) for both calls to `isNonEmptyOptional`. |
971 | 123 | !MustNonEmpty1 && !MustNonEmpty294 && isEmptyOptional(Val1, Env1)45 && |
972 | 123 | isEmptyOptional(Val2, Env2)25 ) |
973 | 12 | MergedEnv.addToFlowCondition(MergedEnv.makeNot(HasValueVal)); |
974 | 156 | setHasValue(MergedVal, HasValueVal); |
975 | 156 | return true; |
976 | 294 | } |
977 | | |
978 | | Value *UncheckedOptionalAccessModel::widen(QualType Type, Value &Prev, |
979 | | const Environment &PrevEnv, |
980 | | Value &Current, |
981 | 33 | Environment &CurrentEnv) { |
982 | 33 | switch (compare(Type, Prev, PrevEnv, Current, CurrentEnv)) { |
983 | 3 | case ComparisonResult::Same: |
984 | 3 | return &Prev; |
985 | 30 | case ComparisonResult::Different: |
986 | 30 | if (auto *PrevHasVal = |
987 | 30 | cast_or_null<BoolValue>(Prev.getProperty("has_value"))) { |
988 | 30 | if (isa<TopBoolValue>(PrevHasVal)) |
989 | 15 | return &Prev; |
990 | 30 | } |
991 | 15 | if (auto *CurrentHasVal = |
992 | 15 | cast_or_null<BoolValue>(Current.getProperty("has_value"))) { |
993 | 15 | if (isa<TopBoolValue>(CurrentHasVal)) |
994 | 0 | return &Current; |
995 | 15 | } |
996 | 15 | return &createOptionalValue(CurrentEnv, CurrentEnv.makeTopBoolValue()); |
997 | 0 | case ComparisonResult::Unknown: |
998 | 0 | return nullptr; |
999 | 33 | } |
1000 | 0 | llvm_unreachable("all cases covered in switch"); |
1001 | 0 | } |
1002 | | |
1003 | | UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser( |
1004 | | UncheckedOptionalAccessModelOptions Options) |
1005 | 351 | : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {} |
1006 | | |
1007 | | std::vector<SourceLocation> UncheckedOptionalAccessDiagnoser::diagnose( |
1008 | 5.67k | ASTContext &Ctx, const CFGElement *Elt, const Environment &Env) { |
1009 | 5.67k | return DiagnoseMatchSwitch(*Elt, Ctx, Env); |
1010 | 5.67k | } |
1011 | | |
1012 | | } // namespace dataflow |
1013 | | } // namespace clang |