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
38namespace clang {
39namespace 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")
43template <class... NameTypes>
44static 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
62static 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
90static 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
107static bool isSupportedOptionalType(QualType Ty) {
108 const CXXRecordDecl *Optional =
109 getOptionalBaseClass(RD: Ty->getAsCXXRecordDecl());
110 return Optional != nullptr;
111}
112
113namespace {
114
115using namespace ::clang::ast_matchers;
116
117using LatticeTransferState = TransferState<UncheckedOptionalAccessLattice>;
118
119AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(RD: Node); }
120
121AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {
122 return getOptionalBaseClass(RD: &Node) != nullptr;
123}
124
125auto desugarsToOptionalType() {
126 return hasUnqualifiedDesugaredType(
127 InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl(optionalClass()))));
128}
129
130auto desugarsToOptionalOrDerivedType() {
131 return hasUnqualifiedDesugaredType(
132 InnerMatcher: recordType(hasDeclaration(InnerMatcher: cxxRecordDecl(optionalOrDerivedClass()))));
133}
134
135auto hasOptionalType() { return hasType(InnerMatcher: desugarsToOptionalType()); }
136
137/// Matches any of the spellings of the optional types and sugar, aliases,
138/// derived classes, etc.
139auto hasOptionalOrDerivedType() {
140 return hasType(InnerMatcher: desugarsToOptionalOrDerivedType());
141}
142
143QualType 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
200QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) {
201 return getPublicType(E: MCE.getImplicitObjectArgument());
202}
203
204AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType,
205 ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
206 return InnerMatcher.matches(Node: getPublicReceiverType(MCE: Node), Finder, Builder);
207}
208
209auto 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
218auto 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
227auto 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
235auto nulloptTypeDecl() {
236 return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",
237 "base::nullopt_t", "folly::None",
238 "bsl::nullopt_t"));
239}
240
241auto hasNulloptType() { return hasType(InnerMatcher: nulloptTypeDecl()); }
242
243auto 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
249auto isOptionalNulloptConstructor() {
250 return cxxConstructExpr(
251 hasDeclaration(InnerMatcher: cxxConstructorDecl(parameterCountIs(N: 1),
252 hasParameter(N: 0, InnerMatcher: hasNulloptType()))),
253 hasOptionalOrDerivedType());
254}
255
256auto isOptionalInPlaceConstructor() {
257 return cxxConstructExpr(hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: inPlaceClass())),
258 hasOptionalOrDerivedType());
259}
260
261auto 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
269auto 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
278auto 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
285auto 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
292auto isStdForwardCall() {
293 return callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "std::forward"))),
294 argumentCountIs(N: 1),
295 hasArgument(N: 0, InnerMatcher: hasOptionalOrDerivedType()));
296}
297
298constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
299
300auto 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
312auto 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
335auto isZeroParamConstMemberCall() {
336 return cxxMemberCallExpr(
337 callee(InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst())));
338}
339
340auto isZeroParamConstMemberOperatorCall() {
341 return cxxOperatorCallExpr(
342 callee(InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst())));
343}
344
345auto isNonConstMemberCall() {
346 return cxxMemberCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst()))));
347}
348
349auto isNonConstMemberOperatorCall() {
350 return cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst()))));
351}
352
353auto isCallReturningOptional() {
354 return callExpr(hasType(InnerMatcher: qualType(
355 anyOf(desugarsToOptionalOrDerivedType(),
356 referenceType(pointee(desugarsToOptionalOrDerivedType()))))));
357}
358
359template <typename L, typename R>
360auto 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.
368const 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
378StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {
379 return OptionalLoc.getSyntheticField(Name: "has_value");
380}
381
382StorageLocation &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`.
388void 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.
395BoolValue *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
407QualType 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.
416int 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
426StorageLocation *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
436void 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
445void 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
453void 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
460void 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
469void 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.
480void 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
499void 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
517void 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
531void 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.
554bool 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
613void 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
624void 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
631void 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
639void 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
668void 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
675void 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
683void 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`.
692BoolValue &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
718void 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
730void 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
742void 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
753void transferNulloptAssignment(const CXXOperatorCallExpr *E,
754 const MatchFinder::MatchResult &,
755 LatticeTransferState &State) {
756 transferAssignment(E, HasValueVal&: State.Env.getBoolLiteralValue(Value: false), State);
757}
758
759void 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
793void 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
801void 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
809void 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
817const 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
839void 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
857void 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
870void 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
883std::optional<StatementMatcher>
884ignorableOptional(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
895StatementMatcher
896valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
897 return isOptionalMemberCallWithNameMatcher(matcher: hasName(Name: "value"),
898 Ignorable: IgnorableOptional);
899}
900
901StatementMatcher
902valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {
903 return expr(anyOf(isOptionalOperatorCallWithName(operator_name: "*", Ignorable: IgnorableOptional),
904 isOptionalOperatorCallWithName(operator_name: "->", Ignorable: IgnorableOptional)));
905}
906
907auto 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
1120llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
1121diagnoseUnwrapCall(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
1137auto 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
1166ast_matchers::DeclarationMatcher
1167UncheckedOptionalAccessModel::optionalClassDecl() {
1168 return cxxRecordDecl(optionalClass());
1169}
1170
1171UncheckedOptionalAccessModel::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
1187void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt,
1188 UncheckedOptionalAccessLattice &L,
1189 Environment &Env) {
1190 LatticeTransferState State(L, Env);
1191 TransferMatchSwitch(Elt, getASTContext(), State);
1192}
1193
1194UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(
1195 UncheckedOptionalAccessModelOptions Options)
1196 : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
1197
1198} // namespace dataflow
1199} // namespace clang
1200

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp