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/AST/Type.h" |
21 | #include "clang/ASTMatchers/ASTMatchers.h" |
22 | #include "clang/ASTMatchers/ASTMatchersMacros.h" |
23 | #include "clang/Analysis/CFG.h" |
24 | #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h" |
25 | #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" |
26 | #include "clang/Analysis/FlowSensitive/Formula.h" |
27 | #include "clang/Analysis/FlowSensitive/RecordOps.h" |
28 | #include "clang/Analysis/FlowSensitive/SmartPointerAccessorCaching.h" |
29 | #include "clang/Analysis/FlowSensitive/StorageLocation.h" |
30 | #include "clang/Analysis/FlowSensitive/Value.h" |
31 | #include "clang/Basic/OperatorKinds.h" |
32 | #include "clang/Basic/SourceLocation.h" |
33 | #include "llvm/ADT/StringRef.h" |
34 | #include "llvm/Support/ErrorHandling.h" |
35 | #include <cassert> |
36 | #include <optional> |
37 | |
38 | namespace clang { |
39 | namespace dataflow { |
40 | |
41 | // Note: the Names appear in reverse order. E.g., to check |
42 | // if NS is foo::bar::, call isFullyQualifiedNamespaceEqualTo(NS, "bar", "foo") |
43 | template <class... NameTypes> |
44 | static bool isFullyQualifiedNamespaceEqualTo(const NamespaceDecl &NS, |
45 | llvm::StringRef Name, |
46 | NameTypes... Names) { |
47 | if (!(NS.getDeclName().isIdentifier() && NS.getName() == Name && |
48 | NS.getParent() != nullptr)) |
49 | return false; |
50 | |
51 | if constexpr (sizeof...(NameTypes) > 0) { |
52 | if (NS.getParent()->isTranslationUnit()) |
53 | return false; |
54 | if (const auto *NextNS = dyn_cast_or_null<NamespaceDecl>(NS.getParent())) |
55 | return isFullyQualifiedNamespaceEqualTo(*NextNS, Names...); |
56 | return false; |
57 | } else { |
58 | return NS.getParent()->isTranslationUnit(); |
59 | } |
60 | } |
61 | |
62 | static bool hasOptionalClassName(const CXXRecordDecl &RD) { |
63 | if (!RD.getDeclName().isIdentifier()) |
64 | return false; |
65 | |
66 | if (RD.getName() == "optional") { |
67 | if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext())) |
68 | return N->isStdNamespace() || |
69 | isFullyQualifiedNamespaceEqualTo(*N, "absl") || |
70 | isFullyQualifiedNamespaceEqualTo(*N, "bsl"); |
71 | return false; |
72 | } |
73 | |
74 | if (RD.getName() == "Optional") { |
75 | // Check whether namespace is "::base" or "::folly". |
76 | const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); |
77 | return N != nullptr && (isFullyQualifiedNamespaceEqualTo(*N, "base") || |
78 | isFullyQualifiedNamespaceEqualTo(*N, "folly")); |
79 | } |
80 | |
81 | if (RD.getName() == "NullableValue") { |
82 | const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()); |
83 | return N != nullptr && |
84 | isFullyQualifiedNamespaceEqualTo(*N, "bdlb", "BloombergLP"); |
85 | } |
86 | |
87 | return false; |
88 | } |
89 | |
90 | static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) { |
91 | if (RD == nullptr) |
92 | return nullptr; |
93 | if (hasOptionalClassName(RD: *RD)) |
94 | return RD; |
95 | |
96 | if (!RD->hasDefinition()) |
97 | return nullptr; |
98 | |
99 | for (const CXXBaseSpecifier &Base : RD->bases()) |
100 | if (const CXXRecordDecl *BaseClass = |
101 | getOptionalBaseClass(RD: Base.getType()->getAsCXXRecordDecl())) |
102 | return BaseClass; |
103 | |
104 | return nullptr; |
105 | } |
106 | |
107 | static bool isSupportedOptionalType(QualType Ty) { |
108 | const CXXRecordDecl *Optional = |
109 | getOptionalBaseClass(RD: Ty->getAsCXXRecordDecl()); |
110 | return Optional != nullptr; |
111 | } |
112 | |
113 | namespace { |
114 | |
115 | using namespace ::clang::ast_matchers; |
116 | |
117 | using LatticeTransferState = TransferState<UncheckedOptionalAccessLattice>; |
118 | |
119 | AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(RD: Node); } |
120 | |
121 | AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) { |
122 | return getOptionalBaseClass(RD: &Node) != nullptr; |
123 | } |
124 | |
125 | auto desugarsToOptionalType() { |
126 | return hasUnqualifiedDesugaredType( |
127 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl(optionalClass())))); |
128 | } |
129 | |
130 | auto desugarsToOptionalOrDerivedType() { |
131 | return hasUnqualifiedDesugaredType( |
132 | InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl(optionalOrDerivedClass())))); |
133 | } |
134 | |
135 | auto hasOptionalType() { return hasType(InnerMatcher: desugarsToOptionalType()); } |
136 | |
137 | /// Matches any of the spellings of the optional types and sugar, aliases, |
138 | /// derived classes, etc. |
139 | auto hasOptionalOrDerivedType() { |
140 | return hasType(InnerMatcher: desugarsToOptionalOrDerivedType()); |
141 | } |
142 | |
143 | QualType getPublicType(const Expr *E) { |
144 | auto *Cast = dyn_cast<ImplicitCastExpr>(Val: E->IgnoreParens()); |
145 | if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase) { |
146 | QualType Ty = E->getType(); |
147 | if (Ty->isPointerType()) |
148 | return Ty->getPointeeType(); |
149 | return Ty; |
150 | } |
151 | |
152 | // Is the derived type that we're casting from the type of `*this`? In this |
153 | // special case, we can upcast to the base class even if the base is |
154 | // non-public. |
155 | bool CastingFromThis = isa<CXXThisExpr>(Cast->getSubExpr()); |
156 | |
157 | // Find the least-derived type in the path (i.e. the last entry in the list) |
158 | // that we can access. |
159 | const CXXBaseSpecifier *PublicBase = nullptr; |
160 | for (const CXXBaseSpecifier *Base : Cast->path()) { |
161 | if (Base->getAccessSpecifier() != AS_public && !CastingFromThis) |
162 | break; |
163 | PublicBase = Base; |
164 | CastingFromThis = false; |
165 | } |
166 | |
167 | if (PublicBase != nullptr) |
168 | return PublicBase->getType(); |
169 | |
170 | // We didn't find any public type that we could cast to. There may be more |
171 | // casts in `getSubExpr()`, so recurse. (If there aren't any more casts, this |
172 | // will return the type of `getSubExpr()`.) |
173 | return getPublicType(Cast->getSubExpr()); |
174 | } |
175 | |
176 | // Returns the least-derived type for the receiver of `MCE` that |
177 | // `MCE.getImplicitObjectArgument()->IgnoreParentImpCasts()` can be downcast to. |
178 | // Effectively, we upcast until we reach a non-public base class, unless that |
179 | // base is a base of `*this`. |
180 | // |
181 | // This is needed to correctly match methods called on types derived from |
182 | // `std::optional`. |
183 | // |
184 | // Say we have a `struct Derived : public std::optional<int> {} d;` For a call |
185 | // `d.has_value()`, the `getImplicitObjectArgument()` looks like this: |
186 | // |
187 | // ImplicitCastExpr 'const std::__optional_storage_base<int>' lvalue |
188 | // | <UncheckedDerivedToBase (optional -> __optional_storage_base)> |
189 | // `-DeclRefExpr 'Derived' lvalue Var 'd' 'Derived' |
190 | // |
191 | // The type of the implicit object argument is `__optional_storage_base` |
192 | // (since this is the internal type that `has_value()` is declared on). If we |
193 | // call `IgnoreParenImpCasts()` on the implicit object argument, we get the |
194 | // `DeclRefExpr`, which has type `Derived`. Neither of these types is |
195 | // `optional`, and hence neither is sufficient for querying whether we are |
196 | // calling a method on `optional`. |
197 | // |
198 | // Instead, starting with the most derived type, we need to follow the chain of |
199 | // casts |
200 | QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) { |
201 | return getPublicType(E: MCE.getImplicitObjectArgument()); |
202 | } |
203 | |
204 | AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType, |
205 | ast_matchers::internal::Matcher<QualType>, InnerMatcher) { |
206 | return InnerMatcher.matches(Node: getPublicReceiverType(MCE: Node), Finder, Builder); |
207 | } |
208 | |
209 | auto isOptionalMemberCallWithNameMatcher( |
210 | ast_matchers::internal::Matcher<NamedDecl> matcher, |
211 | const std::optional<StatementMatcher> &Ignorable = std::nullopt) { |
212 | return cxxMemberCallExpr(Ignorable ? on(InnerMatcher: expr(unless(*Ignorable))) |
213 | : anything(), |
214 | publicReceiverType(InnerMatcher: desugarsToOptionalType()), |
215 | callee(InnerMatcher: cxxMethodDecl(matcher))); |
216 | } |
217 | |
218 | auto isOptionalOperatorCallWithName( |
219 | llvm::StringRef operator_name, |
220 | const std::optional<StatementMatcher> &Ignorable = std::nullopt) { |
221 | return cxxOperatorCallExpr( |
222 | hasOverloadedOperatorName(Name: operator_name), |
223 | callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: optionalClass()))), |
224 | Ignorable ? callExpr(unless(hasArgument(N: 0, InnerMatcher: *Ignorable))) : callExpr()); |
225 | } |
226 | |
227 | auto isMakeOptionalCall() { |
228 | return callExpr( |
229 | callee(InnerMatcher: functionDecl(hasAnyName( |
230 | "std::make_optional", "base::make_optional", "absl::make_optional", |
231 | "folly::make_optional", "bsl::make_optional"))), |
232 | hasOptionalType()); |
233 | } |
234 | |
235 | auto nulloptTypeDecl() { |
236 | return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t", |
237 | "base::nullopt_t", "folly::None", |
238 | "bsl::nullopt_t")); |
239 | } |
240 | |
241 | auto hasNulloptType() { return hasType(InnerMatcher: nulloptTypeDecl()); } |
242 | |
243 | auto inPlaceClass() { |
244 | return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t", |
245 | "base::in_place_t", "folly::in_place_t", |
246 | "bsl::in_place_t")); |
247 | } |
248 | |
249 | auto isOptionalNulloptConstructor() { |
250 | return cxxConstructExpr( |
251 | hasDeclaration(InnerMatcher: cxxConstructorDecl(parameterCountIs(N: 1), |
252 | hasParameter(N: 0, InnerMatcher: hasNulloptType()))), |
253 | hasOptionalOrDerivedType()); |
254 | } |
255 | |
256 | auto isOptionalInPlaceConstructor() { |
257 | return cxxConstructExpr(hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: inPlaceClass())), |
258 | hasOptionalOrDerivedType()); |
259 | } |
260 | |
261 | auto isOptionalValueOrConversionConstructor() { |
262 | return cxxConstructExpr( |
263 | unless(hasDeclaration( |
264 | InnerMatcher: cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))), |
265 | argumentCountIs(N: 1), hasArgument(N: 0, InnerMatcher: unless(hasNulloptType())), |
266 | hasOptionalOrDerivedType()); |
267 | } |
268 | |
269 | auto isOptionalValueOrConversionAssignment() { |
270 | return cxxOperatorCallExpr( |
271 | hasOverloadedOperatorName(Name: "="), |
272 | callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: optionalOrDerivedClass()))), |
273 | unless(hasDeclaration(InnerMatcher: cxxMethodDecl( |
274 | anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))), |
275 | argumentCountIs(N: 2), hasArgument(N: 1, InnerMatcher: unless(hasNulloptType()))); |
276 | } |
277 | |
278 | auto isOptionalNulloptAssignment() { |
279 | return cxxOperatorCallExpr( |
280 | hasOverloadedOperatorName(Name: "="), |
281 | callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: optionalOrDerivedClass()))), |
282 | argumentCountIs(N: 2), hasArgument(N: 1, InnerMatcher: hasNulloptType())); |
283 | } |
284 | |
285 | auto isStdSwapCall() { |
286 | return callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "std::swap"))), |
287 | argumentCountIs(N: 2), |
288 | hasArgument(N: 0, InnerMatcher: hasOptionalOrDerivedType()), |
289 | hasArgument(N: 1, InnerMatcher: hasOptionalOrDerivedType())); |
290 | } |
291 | |
292 | auto isStdForwardCall() { |
293 | return callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "std::forward"))), |
294 | argumentCountIs(N: 1), |
295 | hasArgument(N: 0, InnerMatcher: hasOptionalOrDerivedType())); |
296 | } |
297 | |
298 | constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall"; |
299 | |
300 | auto isValueOrStringEmptyCall() { |
301 | // `opt.value_or("").empty()` |
302 | return cxxMemberCallExpr( |
303 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "empty"))), |
304 | onImplicitObjectArgument(InnerMatcher: ignoringImplicit( |
305 | InnerMatcher: cxxMemberCallExpr(on(InnerMatcher: expr(unless(cxxThisExpr()))), |
306 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "value_or"), |
307 | ofClass(InnerMatcher: optionalClass()))), |
308 | hasArgument(N: 0, InnerMatcher: stringLiteral(hasSize(N: 0)))) |
309 | .bind(ID: ValueOrCallID)))); |
310 | } |
311 | |
312 | auto isValueOrNotEqX() { |
313 | auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) { |
314 | return hasOperands( |
315 | Matcher1: ignoringImplicit( |
316 | InnerMatcher: cxxMemberCallExpr(on(InnerMatcher: expr(unless(cxxThisExpr()))), |
317 | callee(InnerMatcher: cxxMethodDecl(hasName(Name: "value_or"), |
318 | ofClass(InnerMatcher: optionalClass()))), |
319 | hasArgument(N: 0, InnerMatcher: Arg)) |
320 | .bind(ID: ValueOrCallID)), |
321 | Matcher2: ignoringImplicit(InnerMatcher: Arg)); |
322 | }; |
323 | |
324 | // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd |
325 | // support this pattern for any expression, but the AST does not have a |
326 | // generic expression comparison facility, so we specialize to common cases |
327 | // seen in practice. FIXME: define a matcher that compares values across |
328 | // nodes, which would let us generalize this to any `X`. |
329 | return binaryOperation(hasOperatorName(Name: "!="), |
330 | anyOf(ComparesToSame(cxxNullPtrLiteralExpr()), |
331 | ComparesToSame(stringLiteral(hasSize(N: 0))), |
332 | ComparesToSame(integerLiteral(equals(Value: 0))))); |
333 | } |
334 | |
335 | auto isZeroParamConstMemberCall() { |
336 | return cxxMemberCallExpr( |
337 | callee(InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst()))); |
338 | } |
339 | |
340 | auto isZeroParamConstMemberOperatorCall() { |
341 | return cxxOperatorCallExpr( |
342 | callee(InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst()))); |
343 | } |
344 | |
345 | auto isNonConstMemberCall() { |
346 | return cxxMemberCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst())))); |
347 | } |
348 | |
349 | auto isNonConstMemberOperatorCall() { |
350 | return cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst())))); |
351 | } |
352 | |
353 | auto isCallReturningOptional() { |
354 | return callExpr(hasType(InnerMatcher: qualType( |
355 | anyOf(desugarsToOptionalOrDerivedType(), |
356 | referenceType(pointee(desugarsToOptionalOrDerivedType())))))); |
357 | } |
358 | |
359 | template <typename L, typename R> |
360 | auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) { |
361 | return cxxOperatorCallExpr( |
362 | anyOf(hasOverloadedOperatorName(Name: "=="), hasOverloadedOperatorName(Name: "!=")), |
363 | argumentCountIs(N: 2), hasArgument(0, lhs_arg_matcher), |
364 | hasArgument(1, rhs_arg_matcher)); |
365 | } |
366 | |
367 | /// Ensures that `Expr` is mapped to a `BoolValue` and returns its formula. |
368 | const Formula &forceBoolValue(Environment &Env, const Expr &Expr) { |
369 | auto *Value = Env.get<BoolValue>(E: Expr); |
370 | if (Value != nullptr) |
371 | return Value->formula(); |
372 | |
373 | Value = &Env.makeAtomicBoolValue(); |
374 | Env.setValue(E: Expr, Val&: *Value); |
375 | return Value->formula(); |
376 | } |
377 | |
378 | StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) { |
379 | return OptionalLoc.getSyntheticField(Name: "has_value"); |
380 | } |
381 | |
382 | StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) { |
383 | return OptionalLoc.getSyntheticField(Name: "value"); |
384 | } |
385 | |
386 | /// Sets `HasValueVal` as the symbolic value that represents the "has_value" |
387 | /// property of the optional at `OptionalLoc`. |
388 | void setHasValue(RecordStorageLocation &OptionalLoc, BoolValue &HasValueVal, |
389 | Environment &Env) { |
390 | Env.setValue(Loc: locForHasValue(OptionalLoc), Val&: HasValueVal); |
391 | } |
392 | |
393 | /// Returns the symbolic value that represents the "has_value" property of the |
394 | /// optional at `OptionalLoc`. Returns null if `OptionalLoc` is null. |
395 | BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) { |
396 | if (OptionalLoc == nullptr) |
397 | return nullptr; |
398 | StorageLocation &HasValueLoc = locForHasValue(OptionalLoc: *OptionalLoc); |
399 | auto *HasValueVal = Env.get<BoolValue>(Loc: HasValueLoc); |
400 | if (HasValueVal == nullptr) { |
401 | HasValueVal = &Env.makeAtomicBoolValue(); |
402 | Env.setValue(Loc: HasValueLoc, Val&: *HasValueVal); |
403 | } |
404 | return HasValueVal; |
405 | } |
406 | |
407 | QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) { |
408 | auto &CTSD = cast<ClassTemplateSpecializationDecl>(Val: RD); |
409 | return CTSD.getTemplateArgs()[0].getAsType(); |
410 | } |
411 | |
412 | /// Returns the number of optional wrappers in `Type`. |
413 | /// |
414 | /// For example, if `Type` is `optional<optional<int>>`, the result of this |
415 | /// function will be 2. |
416 | int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) { |
417 | const CXXRecordDecl *Optional = |
418 | getOptionalBaseClass(RD: Type->getAsCXXRecordDecl()); |
419 | if (Optional == nullptr) |
420 | return 0; |
421 | return 1 + countOptionalWrappers( |
422 | ASTCtx, |
423 | Type: valueTypeFromOptionalDecl(RD: *Optional).getDesugaredType(Context: ASTCtx)); |
424 | } |
425 | |
426 | StorageLocation *getLocBehindPossiblePointer(const Expr &E, |
427 | const Environment &Env) { |
428 | if (E.isPRValue()) { |
429 | if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Val: Env.getValue(E))) |
430 | return &PointerVal->getPointeeLoc(); |
431 | return nullptr; |
432 | } |
433 | return Env.getStorageLocation(E); |
434 | } |
435 | |
436 | void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, |
437 | LatticeTransferState &State) { |
438 | if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>( |
439 | Val: getLocBehindPossiblePointer(E: *ObjectExpr, Env: State.Env))) { |
440 | if (State.Env.getStorageLocation(E: *UnwrapExpr) == nullptr) |
441 | State.Env.setStorageLocation(E: *UnwrapExpr, Loc&: locForValue(OptionalLoc: *OptionalLoc)); |
442 | } |
443 | } |
444 | |
445 | void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr, |
446 | LatticeTransferState &State) { |
447 | if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>( |
448 | Val: getLocBehindPossiblePointer(E: *ObjectExpr, Env: State.Env))) |
449 | State.Env.setValue( |
450 | E: *UnwrapExpr, Val&: State.Env.create<PointerValue>(args&: locForValue(OptionalLoc: *OptionalLoc))); |
451 | } |
452 | |
453 | void transferMakeOptionalCall(const CallExpr *E, |
454 | const MatchFinder::MatchResult &, |
455 | LatticeTransferState &State) { |
456 | setHasValue(State.Env.getResultObjectLocation(*E), |
457 | State.Env.getBoolLiteralValue(Value: true), State.Env); |
458 | } |
459 | |
460 | void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr, |
461 | const MatchFinder::MatchResult &, |
462 | LatticeTransferState &State) { |
463 | if (auto *HasValueVal = getHasValue( |
464 | Env&: State.Env, OptionalLoc: getImplicitObjectLocation(MCE: *CallExpr, Env: State.Env))) { |
465 | State.Env.setValue(*CallExpr, *HasValueVal); |
466 | } |
467 | } |
468 | |
469 | void transferOptionalIsNullCall(const CXXMemberCallExpr *CallExpr, |
470 | const MatchFinder::MatchResult &, |
471 | LatticeTransferState &State) { |
472 | if (auto *HasValueVal = getHasValue( |
473 | Env&: State.Env, OptionalLoc: getImplicitObjectLocation(MCE: *CallExpr, Env: State.Env))) { |
474 | State.Env.setValue(*CallExpr, State.Env.makeNot(Val&: *HasValueVal)); |
475 | } |
476 | } |
477 | |
478 | /// `ModelPred` builds a logical formula relating the predicate in |
479 | /// `ValueOrPredExpr` to the optional's `has_value` property. |
480 | void transferValueOrImpl( |
481 | const clang::Expr *ValueOrPredExpr, const MatchFinder::MatchResult &Result, |
482 | LatticeTransferState &State, |
483 | const Formula &(*ModelPred)(Environment &Env, const Formula &ExprVal, |
484 | const Formula &HasValueVal)) { |
485 | auto &Env = State.Env; |
486 | |
487 | const auto *MCE = |
488 | Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ID: ValueOrCallID); |
489 | |
490 | auto *HasValueVal = |
491 | getHasValue(Env&: State.Env, OptionalLoc: getImplicitObjectLocation(MCE: *MCE, Env: State.Env)); |
492 | if (HasValueVal == nullptr) |
493 | return; |
494 | |
495 | Env.assume(ModelPred(Env, forceBoolValue(Env, Expr: *ValueOrPredExpr), |
496 | HasValueVal->formula())); |
497 | } |
498 | |
499 | void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr, |
500 | const MatchFinder::MatchResult &Result, |
501 | LatticeTransferState &State) { |
502 | return transferValueOrImpl(ValueOrPredExpr: ComparisonExpr, Result, State, |
503 | ModelPred: [](Environment &Env, const Formula &ExprVal, |
504 | const Formula &HasValueVal) -> const Formula & { |
505 | auto &A = Env.arena(); |
506 | // If the result is *not* empty, then we know the |
507 | // optional must have been holding a value. If |
508 | // `ExprVal` is true, though, we don't learn |
509 | // anything definite about `has_value`, so we |
510 | // don't add any corresponding implications to |
511 | // the flow condition. |
512 | return A.makeImplies(LHS: A.makeNot(Val: ExprVal), |
513 | RHS: HasValueVal); |
514 | }); |
515 | } |
516 | |
517 | void transferValueOrNotEqX(const Expr *ComparisonExpr, |
518 | const MatchFinder::MatchResult &Result, |
519 | LatticeTransferState &State) { |
520 | transferValueOrImpl(ValueOrPredExpr: ComparisonExpr, Result, State, |
521 | ModelPred: [](Environment &Env, const Formula &ExprVal, |
522 | const Formula &HasValueVal) -> const Formula & { |
523 | auto &A = Env.arena(); |
524 | // We know that if `(opt.value_or(X) != X)` then |
525 | // `opt.hasValue()`, even without knowing further |
526 | // details about the contents of `opt`. |
527 | return A.makeImplies(LHS: ExprVal, RHS: HasValueVal); |
528 | }); |
529 | } |
530 | |
531 | void transferCallReturningOptional(const CallExpr *E, |
532 | const MatchFinder::MatchResult &Result, |
533 | LatticeTransferState &State) { |
534 | RecordStorageLocation *Loc = nullptr; |
535 | if (E->isPRValue()) { |
536 | Loc = &State.Env.getResultObjectLocation(*E); |
537 | } else { |
538 | Loc = State.Env.get<RecordStorageLocation>(*E); |
539 | if (Loc == nullptr) { |
540 | Loc = &cast<RecordStorageLocation>(Val&: State.Env.createStorageLocation(*E)); |
541 | State.Env.setStorageLocation(*E, *Loc); |
542 | } |
543 | } |
544 | |
545 | if (State.Env.getValue(Loc: locForHasValue(OptionalLoc: *Loc)) != nullptr) |
546 | return; |
547 | |
548 | setHasValue(OptionalLoc&: *Loc, HasValueVal&: State.Env.makeAtomicBoolValue(), Env&: State.Env); |
549 | } |
550 | |
551 | // Returns true if the const accessor is handled by caching. |
552 | // Returns false if we could not cache. We should perform default handling |
553 | // in that case. |
554 | bool handleConstMemberCall(const CallExpr *CE, |
555 | dataflow::RecordStorageLocation *RecordLoc, |
556 | const MatchFinder::MatchResult &Result, |
557 | LatticeTransferState &State) { |
558 | if (RecordLoc == nullptr) |
559 | return false; |
560 | |
561 | // Cache if the const method returns a reference. |
562 | if (CE->isGLValue()) { |
563 | const FunctionDecl *DirectCallee = CE->getDirectCallee(); |
564 | if (DirectCallee == nullptr) |
565 | return false; |
566 | |
567 | // Initialize the optional's "has_value" property to true if the type is |
568 | // optional, otherwise no-op. If we want to support const ref to pointers or |
569 | // bools we should initialize their values here too. |
570 | auto Init = [&](StorageLocation &Loc) { |
571 | if (isSupportedOptionalType(CE->getType())) |
572 | setHasValue(OptionalLoc&: cast<RecordStorageLocation>(Val&: Loc), |
573 | HasValueVal&: State.Env.makeAtomicBoolValue(), Env&: State.Env); |
574 | }; |
575 | StorageLocation &Loc = |
576 | State.Lattice.getOrCreateConstMethodReturnStorageLocation( |
577 | RecordLoc: *RecordLoc, Callee: DirectCallee, Env&: State.Env, Initialize: Init); |
578 | |
579 | State.Env.setStorageLocation(*CE, Loc); |
580 | return true; |
581 | } |
582 | // PRValue cases: |
583 | if (CE->getType()->isBooleanType() || CE->getType()->isPointerType()) { |
584 | // If the const method returns a boolean or pointer type. |
585 | Value *Val = State.Lattice.getOrCreateConstMethodReturnValue(RecordLoc: *RecordLoc, CE, |
586 | Env&: State.Env); |
587 | if (Val == nullptr) |
588 | return false; |
589 | State.Env.setValue(*CE, *Val); |
590 | return true; |
591 | } |
592 | if (isSupportedOptionalType(CE->getType())) { |
593 | // If the const method returns an optional by value. |
594 | const FunctionDecl *DirectCallee = CE->getDirectCallee(); |
595 | if (DirectCallee == nullptr) |
596 | return false; |
597 | StorageLocation &Loc = |
598 | State.Lattice.getOrCreateConstMethodReturnStorageLocation( |
599 | RecordLoc: *RecordLoc, Callee: DirectCallee, Env&: State.Env, Initialize: [&](StorageLocation &Loc) { |
600 | setHasValue(OptionalLoc&: cast<RecordStorageLocation>(Val&: Loc), |
601 | HasValueVal&: State.Env.makeAtomicBoolValue(), Env&: State.Env); |
602 | }); |
603 | // Use copyRecord to link the optional to the result object of the call |
604 | // expression. |
605 | auto &ResultLoc = State.Env.getResultObjectLocation(*CE); |
606 | copyRecord(cast<RecordStorageLocation>(Val&: Loc), ResultLoc, State.Env); |
607 | return true; |
608 | } |
609 | |
610 | return false; |
611 | } |
612 | |
613 | void handleConstMemberCallWithFallbacks( |
614 | const CallExpr *CE, dataflow::RecordStorageLocation *RecordLoc, |
615 | const MatchFinder::MatchResult &Result, LatticeTransferState &State) { |
616 | if (handleConstMemberCall(CE, RecordLoc, Result, State)) |
617 | return; |
618 | // Perform default handling if the call returns an optional, but wasn't |
619 | // handled by caching. |
620 | if (isSupportedOptionalType(CE->getType())) |
621 | transferCallReturningOptional(E: CE, Result, State); |
622 | } |
623 | |
624 | void transferConstMemberCall(const CXXMemberCallExpr *MCE, |
625 | const MatchFinder::MatchResult &Result, |
626 | LatticeTransferState &State) { |
627 | handleConstMemberCallWithFallbacks( |
628 | MCE, dataflow::getImplicitObjectLocation(MCE: *MCE, Env: State.Env), Result, State); |
629 | } |
630 | |
631 | void transferConstMemberOperatorCall(const CXXOperatorCallExpr *OCE, |
632 | const MatchFinder::MatchResult &Result, |
633 | LatticeTransferState &State) { |
634 | auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>( |
635 | State.Env.getStorageLocation(*OCE->getArg(0))); |
636 | handleConstMemberCallWithFallbacks(OCE, RecordLoc, Result, State); |
637 | } |
638 | |
639 | void handleNonConstMemberCall(const CallExpr *CE, |
640 | dataflow::RecordStorageLocation *RecordLoc, |
641 | const MatchFinder::MatchResult &Result, |
642 | LatticeTransferState &State) { |
643 | if (RecordLoc != nullptr) { |
644 | // When a non-const member function is called, clear all (non-const) |
645 | // optional fields of the receiver. Const-qualified fields can't be |
646 | // changed (at least, not without UB). |
647 | for (const auto &[Field, FieldLoc] : RecordLoc->children()) { |
648 | QualType FieldType = Field->getType(); |
649 | if (!FieldType.isConstQualified() && |
650 | isSupportedOptionalType(Ty: Field->getType())) { |
651 | auto *FieldRecordLoc = cast_or_null<RecordStorageLocation>(Val: FieldLoc); |
652 | if (FieldRecordLoc) { |
653 | setHasValue(OptionalLoc&: *FieldRecordLoc, HasValueVal&: State.Env.makeAtomicBoolValue(), |
654 | Env&: State.Env); |
655 | } |
656 | } |
657 | } |
658 | State.Lattice.clearConstMethodReturnValues(RecordLoc: *RecordLoc); |
659 | State.Lattice.clearConstMethodReturnStorageLocations(RecordLoc: *RecordLoc); |
660 | } |
661 | |
662 | // Perform default handling if the call returns an optional. |
663 | if (isSupportedOptionalType(CE->getType())) { |
664 | transferCallReturningOptional(E: CE, Result, State); |
665 | } |
666 | } |
667 | |
668 | void transferValue_NonConstMemberCall(const CXXMemberCallExpr *MCE, |
669 | const MatchFinder::MatchResult &Result, |
670 | LatticeTransferState &State) { |
671 | handleNonConstMemberCall( |
672 | MCE, dataflow::getImplicitObjectLocation(MCE: *MCE, Env: State.Env), Result, State); |
673 | } |
674 | |
675 | void transferValue_NonConstMemberOperatorCall( |
676 | const CXXOperatorCallExpr *OCE, const MatchFinder::MatchResult &Result, |
677 | LatticeTransferState &State) { |
678 | auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>( |
679 | State.Env.getStorageLocation(*OCE->getArg(0))); |
680 | handleNonConstMemberCall(OCE, RecordLoc, Result, State); |
681 | } |
682 | |
683 | void constructOptionalValue(const Expr &E, Environment &Env, |
684 | BoolValue &HasValueVal) { |
685 | RecordStorageLocation &Loc = Env.getResultObjectLocation(RecordPRValue: E); |
686 | setHasValue(OptionalLoc&: Loc, HasValueVal, Env); |
687 | } |
688 | |
689 | /// Returns a symbolic value for the "has_value" property of an `optional<T>` |
690 | /// value that is constructed/assigned from a value of type `U` or `optional<U>` |
691 | /// where `T` is constructible from `U`. |
692 | BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E, |
693 | const MatchFinder::MatchResult &MatchRes, |
694 | LatticeTransferState &State) { |
695 | const int DestTypeOptionalWrappersCount = |
696 | countOptionalWrappers(ASTCtx: *MatchRes.Context, Type: DestType); |
697 | const int ArgTypeOptionalWrappersCount = countOptionalWrappers( |
698 | ASTCtx: *MatchRes.Context, Type: E.getType().getNonReferenceType()); |
699 | |
700 | // Is this an constructor of the form `template<class U> optional(U &&)` / |
701 | // assignment of the form `template<class U> optional& operator=(U &&)` |
702 | // (where `T` is assignable / constructible from `U`)? |
703 | // We recognize this because the number of optionals in the optional being |
704 | // assigned to is different from the function argument type. |
705 | if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount) |
706 | return State.Env.getBoolLiteralValue(Value: true); |
707 | |
708 | // Otherwise, this must be a constructor of the form |
709 | // `template <class U> optional<optional<U> &&)` / assignment of the form |
710 | // `template <class U> optional& operator=(optional<U> &&) |
711 | // (where, again, `T` is assignable / constructible from `U`). |
712 | auto *Loc = State.Env.get<RecordStorageLocation>(E); |
713 | if (auto *HasValueVal = getHasValue(Env&: State.Env, OptionalLoc: Loc)) |
714 | return *HasValueVal; |
715 | return State.Env.makeAtomicBoolValue(); |
716 | } |
717 | |
718 | void transferValueOrConversionConstructor( |
719 | const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes, |
720 | LatticeTransferState &State) { |
721 | assert(E->getNumArgs() > 0); |
722 | |
723 | constructOptionalValue( |
724 | *E, State.Env, |
725 | valueOrConversionHasValue( |
726 | DestType: E->getConstructor()->getThisType()->getPointeeType(), E: *E->getArg(Arg: 0), |
727 | MatchRes, State)); |
728 | } |
729 | |
730 | void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal, |
731 | LatticeTransferState &State) { |
732 | assert(E->getNumArgs() > 0); |
733 | |
734 | if (auto *Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0))) { |
735 | setHasValue(*Loc, HasValueVal, State.Env); |
736 | |
737 | // Assign a storage location for the whole expression. |
738 | State.Env.setStorageLocation(*E, *Loc); |
739 | } |
740 | } |
741 | |
742 | void transferValueOrConversionAssignment( |
743 | const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes, |
744 | LatticeTransferState &State) { |
745 | assert(E->getNumArgs() > 1); |
746 | transferAssignment( |
747 | E, |
748 | valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(), |
749 | *E->getArg(1), MatchRes, State), |
750 | State); |
751 | } |
752 | |
753 | void transferNulloptAssignment(const CXXOperatorCallExpr *E, |
754 | const MatchFinder::MatchResult &, |
755 | LatticeTransferState &State) { |
756 | transferAssignment(E, HasValueVal&: State.Env.getBoolLiteralValue(Value: false), State); |
757 | } |
758 | |
759 | void transferSwap(RecordStorageLocation *Loc1, RecordStorageLocation *Loc2, |
760 | Environment &Env) { |
761 | // We account for cases where one or both of the optionals are not modeled, |
762 | // either lacking associated storage locations, or lacking values associated |
763 | // to such storage locations. |
764 | |
765 | if (Loc1 == nullptr) { |
766 | if (Loc2 != nullptr) |
767 | setHasValue(OptionalLoc&: *Loc2, HasValueVal&: Env.makeAtomicBoolValue(), Env); |
768 | return; |
769 | } |
770 | if (Loc2 == nullptr) { |
771 | setHasValue(OptionalLoc&: *Loc1, HasValueVal&: Env.makeAtomicBoolValue(), Env); |
772 | return; |
773 | } |
774 | |
775 | // Both expressions have locations, though they may not have corresponding |
776 | // values. In that case, we create a fresh value at this point. Note that if |
777 | // two branches both do this, they will not share the value, but it at least |
778 | // allows for local reasoning about the value. To avoid the above, we would |
779 | // need *lazy* value allocation. |
780 | // FIXME: allocate values lazily, instead of just creating a fresh value. |
781 | BoolValue *BoolVal1 = getHasValue(Env, OptionalLoc: Loc1); |
782 | if (BoolVal1 == nullptr) |
783 | BoolVal1 = &Env.makeAtomicBoolValue(); |
784 | |
785 | BoolValue *BoolVal2 = getHasValue(Env, OptionalLoc: Loc2); |
786 | if (BoolVal2 == nullptr) |
787 | BoolVal2 = &Env.makeAtomicBoolValue(); |
788 | |
789 | setHasValue(OptionalLoc&: *Loc1, HasValueVal&: *BoolVal2, Env); |
790 | setHasValue(OptionalLoc&: *Loc2, HasValueVal&: *BoolVal1, Env); |
791 | } |
792 | |
793 | void transferSwapCall(const CXXMemberCallExpr *E, |
794 | const MatchFinder::MatchResult &, |
795 | LatticeTransferState &State) { |
796 | assert(E->getNumArgs() == 1); |
797 | auto *OtherLoc = State.Env.get<RecordStorageLocation>(*E->getArg(0)); |
798 | transferSwap(getImplicitObjectLocation(MCE: *E, Env: State.Env), OtherLoc, State.Env); |
799 | } |
800 | |
801 | void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &, |
802 | LatticeTransferState &State) { |
803 | assert(E->getNumArgs() == 2); |
804 | auto *Arg0Loc = State.Env.get<RecordStorageLocation>(E: *E->getArg(Arg: 0)); |
805 | auto *Arg1Loc = State.Env.get<RecordStorageLocation>(E: *E->getArg(Arg: 1)); |
806 | transferSwap(Loc1: Arg0Loc, Loc2: Arg1Loc, Env&: State.Env); |
807 | } |
808 | |
809 | void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &, |
810 | LatticeTransferState &State) { |
811 | assert(E->getNumArgs() == 1); |
812 | |
813 | if (auto *Loc = State.Env.getStorageLocation(E: *E->getArg(Arg: 0))) |
814 | State.Env.setStorageLocation(*E, *Loc); |
815 | } |
816 | |
817 | const Formula &evaluateEquality(Arena &A, const Formula &EqVal, |
818 | const Formula &LHS, const Formula &RHS) { |
819 | // Logically, an optional<T> object is composed of two values - a `has_value` |
820 | // bit and a value of type T. Equality of optional objects compares both |
821 | // values. Therefore, merely comparing the `has_value` bits isn't sufficient: |
822 | // when two optional objects are engaged, the equality of their respective |
823 | // values of type T matters. Since we only track the `has_value` bits, we |
824 | // can't make any conclusions about equality when we know that two optional |
825 | // objects are engaged. |
826 | // |
827 | // We express this as two facts about the equality: |
828 | // a) EqVal => (LHS & RHS) v (!RHS & !LHS) |
829 | // If they are equal, then either both are set or both are unset. |
830 | // b) (!LHS & !RHS) => EqVal |
831 | // If neither is set, then they are equal. |
832 | // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula. |
833 | return A.makeAnd( |
834 | LHS: A.makeImplies(LHS: EqVal, RHS: A.makeOr(LHS: A.makeAnd(LHS, RHS), |
835 | RHS: A.makeAnd(LHS: A.makeNot(Val: LHS), RHS: A.makeNot(Val: RHS)))), |
836 | RHS: A.makeImplies(LHS: A.makeNot(Val: EqVal), RHS: A.makeOr(LHS, RHS))); |
837 | } |
838 | |
839 | void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr, |
840 | const MatchFinder::MatchResult &, |
841 | LatticeTransferState &State) { |
842 | Environment &Env = State.Env; |
843 | auto &A = Env.arena(); |
844 | auto *CmpValue = &forceBoolValue(Env, *CmpExpr); |
845 | auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0)); |
846 | if (auto *LHasVal = getHasValue(Env, Arg0Loc)) { |
847 | auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1)); |
848 | if (auto *RHasVal = getHasValue(Env, Arg1Loc)) { |
849 | if (CmpExpr->getOperator() == clang::OO_ExclaimEqual) |
850 | CmpValue = &A.makeNot(Val: *CmpValue); |
851 | Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(), |
852 | RHasVal->formula())); |
853 | } |
854 | } |
855 | } |
856 | |
857 | void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr, |
858 | const clang::Expr *E, Environment &Env) { |
859 | auto &A = Env.arena(); |
860 | auto *CmpValue = &forceBoolValue(Env, *CmpExpr); |
861 | auto *Loc = Env.get<RecordStorageLocation>(E: *E); |
862 | if (auto *HasVal = getHasValue(Env, OptionalLoc: Loc)) { |
863 | if (CmpExpr->getOperator() == clang::OO_ExclaimEqual) |
864 | CmpValue = &A.makeNot(Val: *CmpValue); |
865 | Env.assume( |
866 | evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(Value: true))); |
867 | } |
868 | } |
869 | |
870 | void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr, |
871 | const clang::Expr *E, Environment &Env) { |
872 | auto &A = Env.arena(); |
873 | auto *CmpValue = &forceBoolValue(Env, *CmpExpr); |
874 | auto *Loc = Env.get<RecordStorageLocation>(E: *E); |
875 | if (auto *HasVal = getHasValue(Env, OptionalLoc: Loc)) { |
876 | if (CmpExpr->getOperator() == clang::OO_ExclaimEqual) |
877 | CmpValue = &A.makeNot(Val: *CmpValue); |
878 | Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(), |
879 | A.makeLiteral(Value: false))); |
880 | } |
881 | } |
882 | |
883 | std::optional<StatementMatcher> |
884 | ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) { |
885 | if (Options.IgnoreSmartPointerDereference) { |
886 | auto SmartPtrUse = expr(ignoringParenImpCasts(InnerMatcher: cxxOperatorCallExpr( |
887 | anyOf(hasOverloadedOperatorName(Name: "->"), hasOverloadedOperatorName(Name: "*")), |
888 | unless(hasArgument(N: 0, InnerMatcher: expr(hasOptionalType())))))); |
889 | return expr( |
890 | anyOf(SmartPtrUse, memberExpr(hasObjectExpression(InnerMatcher: SmartPtrUse)))); |
891 | } |
892 | return std::nullopt; |
893 | } |
894 | |
895 | StatementMatcher |
896 | valueCall(const std::optional<StatementMatcher> &IgnorableOptional) { |
897 | return isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "value"), |
898 | Ignorable: IgnorableOptional); |
899 | } |
900 | |
901 | StatementMatcher |
902 | valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) { |
903 | return expr(anyOf(isOptionalOperatorCallWithName(operator_name: "*", Ignorable: IgnorableOptional), |
904 | isOptionalOperatorCallWithName(operator_name: "->", Ignorable: IgnorableOptional))); |
905 | } |
906 | |
907 | auto buildTransferMatchSwitch() { |
908 | // FIXME: Evaluate the efficiency of matchers. If using matchers results in a |
909 | // lot of duplicated work (e.g. string comparisons), consider providing APIs |
910 | // that avoid it through memoization. |
911 | return CFGMatchSwitchBuilder<LatticeTransferState>() |
912 | // make_optional |
913 | .CaseOfCFGStmt<CallExpr>(M: isMakeOptionalCall(), A: transferMakeOptionalCall) |
914 | |
915 | // optional::optional (in place) |
916 | .CaseOfCFGStmt<CXXConstructExpr>( |
917 | M: isOptionalInPlaceConstructor(), |
918 | A: [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, |
919 | LatticeTransferState &State) { |
920 | constructOptionalValue(*E, State.Env, |
921 | State.Env.getBoolLiteralValue(Value: true)); |
922 | }) |
923 | // optional::optional(nullopt_t) |
924 | .CaseOfCFGStmt<CXXConstructExpr>( |
925 | M: isOptionalNulloptConstructor(), |
926 | A: [](const CXXConstructExpr *E, const MatchFinder::MatchResult &, |
927 | LatticeTransferState &State) { |
928 | constructOptionalValue(*E, State.Env, |
929 | State.Env.getBoolLiteralValue(Value: false)); |
930 | }) |
931 | // optional::optional (value/conversion) |
932 | .CaseOfCFGStmt<CXXConstructExpr>(M: isOptionalValueOrConversionConstructor(), |
933 | A: transferValueOrConversionConstructor) |
934 | |
935 | // optional::operator= |
936 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
937 | M: isOptionalValueOrConversionAssignment(), |
938 | A: transferValueOrConversionAssignment) |
939 | .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isOptionalNulloptAssignment(), |
940 | A: transferNulloptAssignment) |
941 | |
942 | // optional::value |
943 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
944 | M: valueCall(IgnorableOptional: std::nullopt), |
945 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
946 | LatticeTransferState &State) { |
947 | transferUnwrapCall(E, E->getImplicitObjectArgument(), State); |
948 | }) |
949 | |
950 | // optional::operator* |
951 | .CaseOfCFGStmt<CallExpr>(M: isOptionalOperatorCallWithName(operator_name: "*"), |
952 | A: [](const CallExpr *E, |
953 | const MatchFinder::MatchResult &, |
954 | LatticeTransferState &State) { |
955 | transferUnwrapCall(E, E->getArg(Arg: 0), State); |
956 | }) |
957 | |
958 | // optional::operator-> |
959 | .CaseOfCFGStmt<CallExpr>(M: isOptionalOperatorCallWithName(operator_name: "->"), |
960 | A: [](const CallExpr *E, |
961 | const MatchFinder::MatchResult &, |
962 | LatticeTransferState &State) { |
963 | transferArrowOpCall(E, E->getArg(Arg: 0), State); |
964 | }) |
965 | |
966 | // optional::has_value, optional::hasValue |
967 | // Of the supported optionals only folly::Optional uses hasValue, but this |
968 | // will also pass for other types |
969 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
970 | M: isOptionalMemberCallWithNameMatcher( |
971 | matcher: hasAnyName("has_value", "hasValue")), |
972 | A: transferOptionalHasValueCall) |
973 | |
974 | // optional::operator bool |
975 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
976 | M: isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "operator bool")), |
977 | A: transferOptionalHasValueCall) |
978 | |
979 | // NullableValue::isNull |
980 | // Only NullableValue has isNull |
981 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
982 | M: isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "isNull")), |
983 | A: transferOptionalIsNullCall) |
984 | |
985 | // optional::emplace |
986 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
987 | M: isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "emplace")), |
988 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
989 | LatticeTransferState &State) { |
990 | if (RecordStorageLocation *Loc = |
991 | getImplicitObjectLocation(MCE: *E, Env: State.Env)) { |
992 | setHasValue(OptionalLoc&: *Loc, HasValueVal&: State.Env.getBoolLiteralValue(Value: true), Env&: State.Env); |
993 | } |
994 | }) |
995 | |
996 | // optional::reset |
997 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
998 | M: isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "reset")), |
999 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
1000 | LatticeTransferState &State) { |
1001 | if (RecordStorageLocation *Loc = |
1002 | getImplicitObjectLocation(MCE: *E, Env: State.Env)) { |
1003 | setHasValue(OptionalLoc&: *Loc, HasValueVal&: State.Env.getBoolLiteralValue(Value: false), |
1004 | Env&: State.Env); |
1005 | } |
1006 | }) |
1007 | |
1008 | // optional::swap |
1009 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
1010 | M: isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "swap")), |
1011 | A: transferSwapCall) |
1012 | |
1013 | // std::swap |
1014 | .CaseOfCFGStmt<CallExpr>(M: isStdSwapCall(), A: transferStdSwapCall) |
1015 | |
1016 | // std::forward |
1017 | .CaseOfCFGStmt<CallExpr>(M: isStdForwardCall(), A: transferStdForwardCall) |
1018 | |
1019 | // opt.value_or("").empty() |
1020 | .CaseOfCFGStmt<Expr>(M: isValueOrStringEmptyCall(), |
1021 | A: transferValueOrStringEmptyCall) |
1022 | |
1023 | // opt.value_or(X) != X |
1024 | .CaseOfCFGStmt<Expr>(M: isValueOrNotEqX(), A: transferValueOrNotEqX) |
1025 | |
1026 | // Comparisons (==, !=): |
1027 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1028 | M: isComparisonOperatorCall(lhs_arg_matcher: hasOptionalType(), rhs_arg_matcher: hasOptionalType()), |
1029 | A: transferOptionalAndOptionalCmp) |
1030 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1031 | M: isComparisonOperatorCall(lhs_arg_matcher: hasOptionalType(), rhs_arg_matcher: hasNulloptType()), |
1032 | A: [](const clang::CXXOperatorCallExpr *Cmp, |
1033 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
1034 | transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env); |
1035 | }) |
1036 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1037 | M: isComparisonOperatorCall(lhs_arg_matcher: hasNulloptType(), rhs_arg_matcher: hasOptionalType()), |
1038 | A: [](const clang::CXXOperatorCallExpr *Cmp, |
1039 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
1040 | transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env); |
1041 | }) |
1042 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1043 | M: isComparisonOperatorCall( |
1044 | lhs_arg_matcher: hasOptionalType(), |
1045 | rhs_arg_matcher: unless(anyOf(hasOptionalType(), hasNulloptType()))), |
1046 | A: [](const clang::CXXOperatorCallExpr *Cmp, |
1047 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
1048 | transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env); |
1049 | }) |
1050 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1051 | M: isComparisonOperatorCall( |
1052 | lhs_arg_matcher: unless(anyOf(hasOptionalType(), hasNulloptType())), |
1053 | rhs_arg_matcher: hasOptionalType()), |
1054 | A: [](const clang::CXXOperatorCallExpr *Cmp, |
1055 | const MatchFinder::MatchResult &, LatticeTransferState &State) { |
1056 | transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env); |
1057 | }) |
1058 | |
1059 | // Smart-pointer-like operator* and operator-> calls that may look like |
1060 | // const accessors (below) but need special handling to allow mixing |
1061 | // the accessor calls. |
1062 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1063 | M: isSmartPointerLikeOperatorStar(), |
1064 | A: [](const CXXOperatorCallExpr *E, |
1065 | const MatchFinder::MatchResult &Result, |
1066 | LatticeTransferState &State) { |
1067 | transferSmartPointerLikeCachedDeref( |
1068 | E, |
1069 | dyn_cast_or_null<RecordStorageLocation>( |
1070 | getLocBehindPossiblePointer(*E->getArg(0), State.Env)), |
1071 | State, [](StorageLocation &Loc) {}); |
1072 | }) |
1073 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1074 | M: isSmartPointerLikeOperatorArrow(), |
1075 | A: [](const CXXOperatorCallExpr *E, |
1076 | const MatchFinder::MatchResult &Result, |
1077 | LatticeTransferState &State) { |
1078 | transferSmartPointerLikeCachedGet( |
1079 | E, |
1080 | dyn_cast_or_null<RecordStorageLocation>( |
1081 | getLocBehindPossiblePointer(*E->getArg(0), State.Env)), |
1082 | State, [](StorageLocation &Loc) {}); |
1083 | }) |
1084 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
1085 | M: isSmartPointerLikeValueMethodCall(), |
1086 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result, |
1087 | LatticeTransferState &State) { |
1088 | transferSmartPointerLikeCachedDeref( |
1089 | E, getImplicitObjectLocation(MCE: *E, Env: State.Env), State, |
1090 | [](StorageLocation &Loc) {}); |
1091 | }) |
1092 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
1093 | M: isSmartPointerLikeGetMethodCall(), |
1094 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result, |
1095 | LatticeTransferState &State) { |
1096 | transferSmartPointerLikeCachedGet( |
1097 | E, getImplicitObjectLocation(MCE: *E, Env: State.Env), State, |
1098 | [](StorageLocation &Loc) {}); |
1099 | }) |
1100 | |
1101 | // const accessor calls |
1102 | .CaseOfCFGStmt<CXXMemberCallExpr>(M: isZeroParamConstMemberCall(), |
1103 | A: transferConstMemberCall) |
1104 | .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isZeroParamConstMemberOperatorCall(), |
1105 | A: transferConstMemberOperatorCall) |
1106 | // non-const member calls that may modify the state of an object. |
1107 | .CaseOfCFGStmt<CXXMemberCallExpr>(M: isNonConstMemberCall(), |
1108 | A: transferValue_NonConstMemberCall) |
1109 | .CaseOfCFGStmt<CXXOperatorCallExpr>( |
1110 | M: isNonConstMemberOperatorCall(), |
1111 | A: transferValue_NonConstMemberOperatorCall) |
1112 | |
1113 | // other cases of returning optional |
1114 | .CaseOfCFGStmt<CallExpr>(M: isCallReturningOptional(), |
1115 | A: transferCallReturningOptional) |
1116 | |
1117 | .Build(); |
1118 | } |
1119 | |
1120 | llvm::SmallVector<UncheckedOptionalAccessDiagnostic> |
1121 | diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) { |
1122 | if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>( |
1123 | Val: getLocBehindPossiblePointer(E: *ObjectExpr, Env))) { |
1124 | auto *Prop = Env.getValue(Loc: locForHasValue(OptionalLoc: *OptionalLoc)); |
1125 | if (auto *HasValueVal = cast_or_null<BoolValue>(Val: Prop)) { |
1126 | if (Env.proves(HasValueVal->formula())) |
1127 | return {}; |
1128 | } |
1129 | } |
1130 | |
1131 | // Record that this unwrap is *not* provably safe. |
1132 | // FIXME: include the name of the optional (if applicable). |
1133 | auto Range = CharSourceRange::getTokenRange(ObjectExpr->getSourceRange()); |
1134 | return {UncheckedOptionalAccessDiagnostic{Range}}; |
1135 | } |
1136 | |
1137 | auto buildDiagnoseMatchSwitch( |
1138 | const UncheckedOptionalAccessModelOptions &Options) { |
1139 | // FIXME: Evaluate the efficiency of matchers. If using matchers results in a |
1140 | // lot of duplicated work (e.g. string comparisons), consider providing APIs |
1141 | // that avoid it through memoization. |
1142 | auto IgnorableOptional = ignorableOptional(Options); |
1143 | return CFGMatchSwitchBuilder< |
1144 | const Environment, |
1145 | llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>() |
1146 | // optional::value |
1147 | .CaseOfCFGStmt<CXXMemberCallExpr>( |
1148 | M: valueCall(IgnorableOptional), |
1149 | A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, |
1150 | const Environment &Env) { |
1151 | return diagnoseUnwrapCall(ObjectExpr: E->getImplicitObjectArgument(), Env); |
1152 | }) |
1153 | |
1154 | // optional::operator*, optional::operator-> |
1155 | .CaseOfCFGStmt<CallExpr>(M: valueOperatorCall(IgnorableOptional), |
1156 | A: [](const CallExpr *E, |
1157 | const MatchFinder::MatchResult &, |
1158 | const Environment &Env) { |
1159 | return diagnoseUnwrapCall(ObjectExpr: E->getArg(Arg: 0), Env); |
1160 | }) |
1161 | .Build(); |
1162 | } |
1163 | |
1164 | } // namespace |
1165 | |
1166 | ast_matchers::DeclarationMatcher |
1167 | UncheckedOptionalAccessModel::optionalClassDecl() { |
1168 | return cxxRecordDecl(optionalClass()); |
1169 | } |
1170 | |
1171 | UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx, |
1172 | Environment &Env) |
1173 | : DataflowAnalysis<UncheckedOptionalAccessModel, |
1174 | UncheckedOptionalAccessLattice>(Ctx), |
1175 | TransferMatchSwitch(buildTransferMatchSwitch()) { |
1176 | Env.getDataflowAnalysisContext().setSyntheticFieldCallback( |
1177 | [&Ctx](QualType Ty) -> llvm::StringMap<QualType> { |
1178 | const CXXRecordDecl *Optional = |
1179 | getOptionalBaseClass(RD: Ty->getAsCXXRecordDecl()); |
1180 | if (Optional == nullptr) |
1181 | return {}; |
1182 | return {{"value", valueTypeFromOptionalDecl(RD: *Optional)}, |
1183 | {"has_value", Ctx.BoolTy}}; |
1184 | }); |
1185 | } |
1186 | |
1187 | void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt, |
1188 | UncheckedOptionalAccessLattice &L, |
1189 | Environment &Env) { |
1190 | LatticeTransferState State(L, Env); |
1191 | TransferMatchSwitch(Elt, getASTContext(), State); |
1192 | } |
1193 | |
1194 | UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser( |
1195 | UncheckedOptionalAccessModelOptions Options) |
1196 | : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {} |
1197 | |
1198 | } // namespace dataflow |
1199 | } // namespace clang |
1200 |
Definitions
- isFullyQualifiedNamespaceEqualTo
- hasOptionalClassName
- getOptionalBaseClass
- isSupportedOptionalType
- optionalClass
- optionalOrDerivedClass
- desugarsToOptionalType
- desugarsToOptionalOrDerivedType
- hasOptionalType
- hasOptionalOrDerivedType
- getPublicType
- getPublicReceiverType
- publicReceiverType
- isOptionalMemberCallWithNameMatcher
- isOptionalOperatorCallWithName
- isMakeOptionalCall
- nulloptTypeDecl
- hasNulloptType
- inPlaceClass
- isOptionalNulloptConstructor
- isOptionalInPlaceConstructor
- isOptionalValueOrConversionConstructor
- isOptionalValueOrConversionAssignment
- isOptionalNulloptAssignment
- isStdSwapCall
- isStdForwardCall
- ValueOrCallID
- isValueOrStringEmptyCall
- isValueOrNotEqX
- isZeroParamConstMemberCall
- isZeroParamConstMemberOperatorCall
- isNonConstMemberCall
- isNonConstMemberOperatorCall
- isCallReturningOptional
- isComparisonOperatorCall
- forceBoolValue
- locForHasValue
- locForValue
- setHasValue
- getHasValue
- valueTypeFromOptionalDecl
- countOptionalWrappers
- getLocBehindPossiblePointer
- transferUnwrapCall
- transferArrowOpCall
- transferMakeOptionalCall
- transferOptionalHasValueCall
- transferOptionalIsNullCall
- transferValueOrImpl
- transferValueOrStringEmptyCall
- transferValueOrNotEqX
- transferCallReturningOptional
- handleConstMemberCall
- handleConstMemberCallWithFallbacks
- transferConstMemberCall
- transferConstMemberOperatorCall
- handleNonConstMemberCall
- transferValue_NonConstMemberCall
- transferValue_NonConstMemberOperatorCall
- constructOptionalValue
- valueOrConversionHasValue
- transferValueOrConversionConstructor
- transferAssignment
- transferValueOrConversionAssignment
- transferNulloptAssignment
- transferSwap
- transferSwapCall
- transferStdSwapCall
- transferStdForwardCall
- evaluateEquality
- transferOptionalAndOptionalCmp
- transferOptionalAndValueCmp
- transferOptionalAndNulloptCmp
- ignorableOptional
- valueCall
- valueOperatorCall
- buildTransferMatchSwitch
- diagnoseUnwrapCall
- buildDiagnoseMatchSwitch
- optionalClassDecl
- UncheckedOptionalAccessModel
- transfer
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more