1 //===-- UncheckedOptionalAccessModel.cpp ------------------------*- C++ -*-===//
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7 //===----------------------------------------------------------------------===//
9 // This file defines a dataflow analysis that detects unsafe uses of optional
12 //===----------------------------------------------------------------------===//
14 #include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
15 #include "clang/AST/ASTContext.h"
16 #include "clang/AST/DeclCXX.h"
17 #include "clang/AST/Expr.h"
18 #include "clang/AST/ExprCXX.h"
19 #include "clang/AST/Stmt.h"
20 #include "clang/ASTMatchers/ASTMatchers.h"
21 #include "clang/ASTMatchers/ASTMatchersMacros.h"
22 #include "clang/Analysis/CFG.h"
23 #include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
24 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
25 #include "clang/Analysis/FlowSensitive/Formula.h"
26 #include "clang/Analysis/FlowSensitive/NoopLattice.h"
27 #include "clang/Analysis/FlowSensitive/StorageLocation.h"
28 #include "clang/Analysis/FlowSensitive/Value.h"
29 #include "clang/Basic/SourceLocation.h"
30 #include "llvm/ADT/StringRef.h"
31 #include "llvm/Support/Casting.h"
32 #include "llvm/Support/ErrorHandling.h"
41 static bool isTopLevelNamespaceWithName(const NamespaceDecl
&NS
,
42 llvm::StringRef Name
) {
43 return NS
.getDeclName().isIdentifier() && NS
.getName() == Name
&&
44 NS
.getParent() != nullptr && NS
.getParent()->isTranslationUnit();
47 static bool hasOptionalClassName(const CXXRecordDecl
&RD
) {
48 if (!RD
.getDeclName().isIdentifier())
51 if (RD
.getName() == "optional") {
52 if (const auto *N
= dyn_cast_or_null
<NamespaceDecl
>(RD
.getDeclContext()))
53 return N
->isStdNamespace() || isTopLevelNamespaceWithName(*N
, "absl");
57 if (RD
.getName() == "Optional") {
58 // Check whether namespace is "::base" or "::folly".
59 const auto *N
= dyn_cast_or_null
<NamespaceDecl
>(RD
.getDeclContext());
60 return N
!= nullptr && (isTopLevelNamespaceWithName(*N
, "base") ||
61 isTopLevelNamespaceWithName(*N
, "folly"));
69 using namespace ::clang::ast_matchers
;
70 using LatticeTransferState
= TransferState
<NoopLattice
>;
72 AST_MATCHER(CXXRecordDecl
, hasOptionalClassNameMatcher
) {
73 return hasOptionalClassName(Node
);
76 DeclarationMatcher
optionalClass() {
77 return classTemplateSpecializationDecl(
78 hasOptionalClassNameMatcher(),
79 hasTemplateArgument(0, refersToType(type().bind("T"))));
82 auto optionalOrAliasType() {
83 return hasUnqualifiedDesugaredType(
84 recordType(hasDeclaration(optionalClass())));
87 /// Matches any of the spellings of the optional types and sugar, aliases, etc.
88 auto hasOptionalType() { return hasType(optionalOrAliasType()); }
90 auto isOptionalMemberCallWithNameMatcher(
91 ast_matchers::internal::Matcher
<NamedDecl
> matcher
,
92 const std::optional
<StatementMatcher
> &Ignorable
= std::nullopt
) {
93 auto Exception
= unless(Ignorable
? expr(anyOf(*Ignorable
, cxxThisExpr()))
95 return cxxMemberCallExpr(
97 anyOf(hasOptionalType(),
98 hasType(pointerType(pointee(optionalOrAliasType())))))),
99 callee(cxxMethodDecl(matcher
)));
102 auto isOptionalOperatorCallWithName(
103 llvm::StringRef operator_name
,
104 const std::optional
<StatementMatcher
> &Ignorable
= std::nullopt
) {
105 return cxxOperatorCallExpr(
106 hasOverloadedOperatorName(operator_name
),
107 callee(cxxMethodDecl(ofClass(optionalClass()))),
108 Ignorable
? callExpr(unless(hasArgument(0, *Ignorable
))) : callExpr());
111 auto isMakeOptionalCall() {
112 return callExpr(callee(functionDecl(hasAnyName(
113 "std::make_optional", "base::make_optional",
114 "absl::make_optional", "folly::make_optional"))),
118 auto nulloptTypeDecl() {
119 return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",
120 "base::nullopt_t", "folly::None"));
123 auto hasNulloptType() { return hasType(nulloptTypeDecl()); }
125 // `optional` or `nullopt_t`
126 auto hasAnyOptionalType() {
127 return hasType(hasUnqualifiedDesugaredType(
128 recordType(hasDeclaration(anyOf(nulloptTypeDecl(), optionalClass())))));
131 auto inPlaceClass() {
132 return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",
133 "base::in_place_t", "folly::in_place_t"));
136 auto isOptionalNulloptConstructor() {
137 return cxxConstructExpr(
139 hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
140 hasParameter(0, hasNulloptType()))));
143 auto isOptionalInPlaceConstructor() {
144 return cxxConstructExpr(hasOptionalType(),
145 hasArgument(0, hasType(inPlaceClass())));
148 auto isOptionalValueOrConversionConstructor() {
149 return cxxConstructExpr(
151 unless(hasDeclaration(
152 cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
153 argumentCountIs(1), hasArgument(0, unless(hasNulloptType())));
156 auto isOptionalValueOrConversionAssignment() {
157 return cxxOperatorCallExpr(
158 hasOverloadedOperatorName("="),
159 callee(cxxMethodDecl(ofClass(optionalClass()))),
160 unless(hasDeclaration(cxxMethodDecl(
161 anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
162 argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
165 auto isNulloptConstructor() {
166 return cxxConstructExpr(hasNulloptType(), argumentCountIs(1),
167 hasArgument(0, hasNulloptType()));
170 auto isOptionalNulloptAssignment() {
171 return cxxOperatorCallExpr(hasOverloadedOperatorName("="),
172 callee(cxxMethodDecl(ofClass(optionalClass()))),
174 hasArgument(1, hasNulloptType()));
177 auto isStdSwapCall() {
178 return callExpr(callee(functionDecl(hasName("std::swap"))),
179 argumentCountIs(2), hasArgument(0, hasOptionalType()),
180 hasArgument(1, hasOptionalType()));
183 auto isStdForwardCall() {
184 return callExpr(callee(functionDecl(hasName("std::forward"))),
185 argumentCountIs(1), hasArgument(0, hasOptionalType()));
188 constexpr llvm::StringLiteral ValueOrCallID
= "ValueOrCall";
190 auto isValueOrStringEmptyCall() {
191 // `opt.value_or("").empty()`
192 return cxxMemberCallExpr(
193 callee(cxxMethodDecl(hasName("empty"))),
194 onImplicitObjectArgument(ignoringImplicit(
195 cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
196 callee(cxxMethodDecl(hasName("value_or"),
197 ofClass(optionalClass()))),
198 hasArgument(0, stringLiteral(hasSize(0))))
199 .bind(ValueOrCallID
))));
202 auto isValueOrNotEqX() {
203 auto ComparesToSame
= [](ast_matchers::internal::Matcher
<Stmt
> Arg
) {
206 cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
207 callee(cxxMethodDecl(hasName("value_or"),
208 ofClass(optionalClass()))),
210 .bind(ValueOrCallID
)),
211 ignoringImplicit(Arg
));
214 // `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd
215 // support this pattern for any expression, but the AST does not have a
216 // generic expression comparison facility, so we specialize to common cases
217 // seen in practice. FIXME: define a matcher that compares values across
218 // nodes, which would let us generalize this to any `X`.
219 return binaryOperation(hasOperatorName("!="),
220 anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
221 ComparesToSame(stringLiteral(hasSize(0))),
222 ComparesToSame(integerLiteral(equals(0)))));
225 auto isCallReturningOptional() {
226 return callExpr(hasType(qualType(anyOf(
227 optionalOrAliasType(), referenceType(pointee(optionalOrAliasType()))))));
230 template <typename L
, typename R
>
231 auto isComparisonOperatorCall(L lhs_arg_matcher
, R rhs_arg_matcher
) {
232 return cxxOperatorCallExpr(
233 anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),
234 argumentCountIs(2), hasArgument(0, lhs_arg_matcher
),
235 hasArgument(1, rhs_arg_matcher
));
238 /// Ensures that `Expr` is mapped to a `BoolValue` and returns its formula.
239 const Formula
&forceBoolValue(Environment
&Env
, const Expr
&Expr
) {
240 auto *Value
= cast_or_null
<BoolValue
>(Env
.getValue(Expr
));
241 if (Value
!= nullptr)
242 return Value
->formula();
244 Value
= &Env
.makeAtomicBoolValue();
245 Env
.setValue(Expr
, *Value
);
246 return Value
->formula();
249 /// Sets `HasValueVal` as the symbolic value that represents the "has_value"
250 /// property of the optional value `OptionalVal`.
251 void setHasValue(Value
&OptionalVal
, BoolValue
&HasValueVal
) {
252 OptionalVal
.setProperty("has_value", HasValueVal
);
255 /// Creates a symbolic value for an `optional` value at an existing storage
256 /// location. Uses `HasValueVal` as the symbolic value of the "has_value"
258 RecordValue
&createOptionalValue(RecordStorageLocation
&Loc
,
259 BoolValue
&HasValueVal
, Environment
&Env
) {
260 auto &OptionalVal
= Env
.create
<RecordValue
>(Loc
);
261 Env
.setValue(Loc
, OptionalVal
);
262 setHasValue(OptionalVal
, HasValueVal
);
266 /// Returns the symbolic value that represents the "has_value" property of the
267 /// optional value `OptionalVal`. Returns null if `OptionalVal` is null.
268 BoolValue
*getHasValue(Environment
&Env
, Value
*OptionalVal
) {
269 if (OptionalVal
!= nullptr) {
271 cast_or_null
<BoolValue
>(OptionalVal
->getProperty("has_value"));
272 if (HasValueVal
== nullptr) {
273 HasValueVal
= &Env
.makeAtomicBoolValue();
274 OptionalVal
->setProperty("has_value", *HasValueVal
);
281 /// Returns true if and only if `Type` is an optional type.
282 bool isOptionalType(QualType Type
) {
283 if (!Type
->isRecordType())
285 const CXXRecordDecl
*D
= Type
->getAsCXXRecordDecl();
286 return D
!= nullptr && hasOptionalClassName(*D
);
289 /// Returns the number of optional wrappers in `Type`.
291 /// For example, if `Type` is `optional<optional<int>>`, the result of this
292 /// function will be 2.
293 int countOptionalWrappers(const ASTContext
&ASTCtx
, QualType Type
) {
294 if (!isOptionalType(Type
))
296 return 1 + countOptionalWrappers(
298 cast
<ClassTemplateSpecializationDecl
>(Type
->getAsRecordDecl())
302 .getDesugaredType(ASTCtx
));
305 /// Tries to initialize the `optional`'s value (that is, contents), and return
306 /// its location. Returns nullptr if the value can't be represented.
307 StorageLocation
*maybeInitializeOptionalValueMember(QualType Q
,
310 // The "value" property represents a synthetic field. As such, it needs
311 // `StorageLocation`, like normal fields (and other variables). So, we model
312 // it with a `PointerValue`, since that includes a storage location. Once
313 // the property is set, it will be shared by all environments that access the
314 // `Value` representing the optional (here, `OptionalVal`).
315 if (auto *ValueProp
= OptionalVal
.getProperty("value")) {
316 auto *ValuePtr
= clang::cast
<PointerValue
>(ValueProp
);
317 auto &ValueLoc
= ValuePtr
->getPointeeLoc();
318 if (Env
.getValue(ValueLoc
) != nullptr)
321 // The property was previously set, but the value has been lost. This can
322 // happen in various situations, for example:
323 // - Because of an environment merge (where the two environments mapped the
324 // property to different values, which resulted in them both being
326 // - When two blocks in the CFG, with neither a dominator of the other,
327 // visit the same optional value. (FIXME: This is something we can and
328 // should fix -- see also the lengthy FIXME below.)
329 // - Or even when a block is revisited during testing to collect
330 // per-statement state.
331 // FIXME: This situation means that the optional contents are not shared
332 // between branches and the like. Practically, this lack of sharing
333 // reduces the precision of the model when the contents are relevant to
334 // the check, like another optional or a boolean that influences control
336 if (ValueLoc
.getType()->isRecordType()) {
337 refreshRecordValue(cast
<RecordStorageLocation
>(ValueLoc
), Env
);
340 auto *ValueVal
= Env
.createValue(ValueLoc
.getType());
341 if (ValueVal
== nullptr)
343 Env
.setValue(ValueLoc
, *ValueVal
);
348 auto Ty
= Q
.getNonReferenceType();
349 auto &ValueLoc
= Env
.createObject(Ty
);
350 auto &ValuePtr
= Env
.create
<PointerValue
>(ValueLoc
);
352 // The change we make to the `value` property below may become visible to
353 // other blocks that aren't successors of the current block and therefore
354 // don't see the change we made above mapping `ValueLoc` to `ValueVal`. For
357 // void target(optional<int> oo, bool b) {
358 // // `oo` is associated with a `RecordValue` here, which we will call
361 // // The `has_value` property is set on `OptionalVal` (but not the
362 // // `value` property yet).
363 // if (!oo.has_value()) return;
366 // // Let's assume we transfer the `if` branch first.
368 // // This causes us to call `maybeInitializeOptionalValueMember()`,
369 // // which causes us to set the `value` property on `OptionalVal`
370 // // (which had not been set until this point). This `value` property
371 // // refers to a `PointerValue`, which in turn refers to a
372 // // StorageLocation` that is associated to an `IntegerValue`.
375 // // Let's assume we transfer the `else` branch after the `if` branch.
377 // // We see the `value` property that the `if` branch set on
378 // // `OptionalVal`, but in the environment for this block, the
379 // // `StorageLocation` in the `PointerValue` is not associated with any
385 // This situation is currently "saved" by the code above that checks whether
386 // the `value` property is already set, and if, the `ValueLoc` is not
387 // associated with a `ValueVal`, creates a new `ValueVal`.
389 // However, what we should really do is to make sure that the change to the
390 // `value` property does not "leak" to other blocks that are not successors
391 // of this block. To do this, instead of simply setting the `value` property
392 // on the existing `OptionalVal`, we should create a new `Value` for the
393 // optional, set the property on that, and associate the storage location that
394 // is currently associated with the existing `OptionalVal` with the newly
395 // created `Value` instead.
396 OptionalVal
.setProperty("value", ValuePtr
);
400 void initializeOptionalReference(const Expr
*OptionalExpr
,
401 const MatchFinder::MatchResult
&,
402 LatticeTransferState
&State
) {
403 if (auto *OptionalVal
= State
.Env
.getValue(*OptionalExpr
)) {
404 if (OptionalVal
->getProperty("has_value") == nullptr) {
405 setHasValue(*OptionalVal
, State
.Env
.makeAtomicBoolValue());
410 /// Returns true if and only if `OptionalVal` is initialized and known to be
412 bool isEmptyOptional(const Value
&OptionalVal
, const Environment
&Env
) {
414 cast_or_null
<BoolValue
>(OptionalVal
.getProperty("has_value"));
415 return HasValueVal
!= nullptr &&
416 Env
.proves(Env
.arena().makeNot(HasValueVal
->formula()));
419 /// Returns true if and only if `OptionalVal` is initialized and known to be
420 /// non-empty in `Env`.
421 bool isNonEmptyOptional(const Value
&OptionalVal
, const Environment
&Env
) {
423 cast_or_null
<BoolValue
>(OptionalVal
.getProperty("has_value"));
424 return HasValueVal
!= nullptr && Env
.proves(HasValueVal
->formula());
427 Value
*getValueBehindPossiblePointer(const Expr
&E
, const Environment
&Env
) {
428 Value
*Val
= Env
.getValue(E
);
429 if (auto *PointerVal
= dyn_cast_or_null
<PointerValue
>(Val
))
430 return Env
.getValue(PointerVal
->getPointeeLoc());
434 void transferUnwrapCall(const Expr
*UnwrapExpr
, const Expr
*ObjectExpr
,
435 LatticeTransferState
&State
) {
436 if (auto *OptionalVal
=
437 getValueBehindPossiblePointer(*ObjectExpr
, State
.Env
)) {
438 if (State
.Env
.getStorageLocation(*UnwrapExpr
) == nullptr)
439 if (auto *Loc
= maybeInitializeOptionalValueMember(
440 UnwrapExpr
->getType(), *OptionalVal
, State
.Env
))
441 State
.Env
.setStorageLocation(*UnwrapExpr
, *Loc
);
445 void transferArrowOpCall(const Expr
*UnwrapExpr
, const Expr
*ObjectExpr
,
446 LatticeTransferState
&State
) {
447 if (auto *OptionalVal
=
448 getValueBehindPossiblePointer(*ObjectExpr
, State
.Env
)) {
449 if (auto *Loc
= maybeInitializeOptionalValueMember(
450 UnwrapExpr
->getType()->getPointeeType(), *OptionalVal
, State
.Env
)) {
451 State
.Env
.setValue(*UnwrapExpr
, State
.Env
.create
<PointerValue
>(*Loc
));
456 void transferMakeOptionalCall(const CallExpr
*E
,
457 const MatchFinder::MatchResult
&,
458 LatticeTransferState
&State
) {
460 *E
, createOptionalValue(State
.Env
.getResultObjectLocation(*E
),
461 State
.Env
.getBoolLiteralValue(true), State
.Env
));
464 void transferOptionalHasValueCall(const CXXMemberCallExpr
*CallExpr
,
465 const MatchFinder::MatchResult
&,
466 LatticeTransferState
&State
) {
467 if (auto *HasValueVal
= getHasValue(
468 State
.Env
, getValueBehindPossiblePointer(
469 *CallExpr
->getImplicitObjectArgument(), State
.Env
))) {
470 State
.Env
.setValue(*CallExpr
, *HasValueVal
);
474 /// `ModelPred` builds a logical formula relating the predicate in
475 /// `ValueOrPredExpr` to the optional's `has_value` property.
476 void transferValueOrImpl(
477 const clang::Expr
*ValueOrPredExpr
, const MatchFinder::MatchResult
&Result
,
478 LatticeTransferState
&State
,
479 const Formula
&(*ModelPred
)(Environment
&Env
, const Formula
&ExprVal
,
480 const Formula
&HasValueVal
)) {
481 auto &Env
= State
.Env
;
483 const auto *ObjectArgumentExpr
=
484 Result
.Nodes
.getNodeAs
<clang::CXXMemberCallExpr
>(ValueOrCallID
)
485 ->getImplicitObjectArgument();
487 auto *HasValueVal
= getHasValue(
488 State
.Env
, getValueBehindPossiblePointer(*ObjectArgumentExpr
, State
.Env
));
489 if (HasValueVal
== nullptr)
492 Env
.assume(ModelPred(Env
, forceBoolValue(Env
, *ValueOrPredExpr
),
493 HasValueVal
->formula()));
496 void transferValueOrStringEmptyCall(const clang::Expr
*ComparisonExpr
,
497 const MatchFinder::MatchResult
&Result
,
498 LatticeTransferState
&State
) {
499 return transferValueOrImpl(ComparisonExpr
, Result
, State
,
500 [](Environment
&Env
, const Formula
&ExprVal
,
501 const Formula
&HasValueVal
) -> const Formula
& {
502 auto &A
= Env
.arena();
503 // If the result is *not* empty, then we know the
504 // optional must have been holding a value. If
505 // `ExprVal` is true, though, we don't learn
506 // anything definite about `has_value`, so we
507 // don't add any corresponding implications to
508 // the flow condition.
509 return A
.makeImplies(A
.makeNot(ExprVal
),
514 void transferValueOrNotEqX(const Expr
*ComparisonExpr
,
515 const MatchFinder::MatchResult
&Result
,
516 LatticeTransferState
&State
) {
517 transferValueOrImpl(ComparisonExpr
, Result
, State
,
518 [](Environment
&Env
, const Formula
&ExprVal
,
519 const Formula
&HasValueVal
) -> const Formula
& {
520 auto &A
= Env
.arena();
521 // We know that if `(opt.value_or(X) != X)` then
522 // `opt.hasValue()`, even without knowing further
523 // details about the contents of `opt`.
524 return A
.makeImplies(ExprVal
, HasValueVal
);
528 void transferCallReturningOptional(const CallExpr
*E
,
529 const MatchFinder::MatchResult
&Result
,
530 LatticeTransferState
&State
) {
531 if (State
.Env
.getValue(*E
) != nullptr)
534 RecordStorageLocation
*Loc
= nullptr;
535 if (E
->isPRValue()) {
536 Loc
= &State
.Env
.getResultObjectLocation(*E
);
538 Loc
= cast_or_null
<RecordStorageLocation
>(State
.Env
.getStorageLocation(*E
));
539 if (Loc
== nullptr) {
540 Loc
= &cast
<RecordStorageLocation
>(State
.Env
.createStorageLocation(*E
));
541 State
.Env
.setStorageLocation(*E
, *Loc
);
546 createOptionalValue(*Loc
, State
.Env
.makeAtomicBoolValue(), State
.Env
);
548 State
.Env
.setValue(*E
, Val
);
551 void constructOptionalValue(const Expr
&E
, Environment
&Env
,
552 BoolValue
&HasValueVal
) {
553 RecordStorageLocation
&Loc
= Env
.getResultObjectLocation(E
);
554 Env
.setValue(E
, createOptionalValue(Loc
, HasValueVal
, Env
));
557 /// Returns a symbolic value for the "has_value" property of an `optional<T>`
558 /// value that is constructed/assigned from a value of type `U` or `optional<U>`
559 /// where `T` is constructible from `U`.
560 BoolValue
&valueOrConversionHasValue(const FunctionDecl
&F
, const Expr
&E
,
561 const MatchFinder::MatchResult
&MatchRes
,
562 LatticeTransferState
&State
) {
563 assert(F
.getTemplateSpecializationArgs() != nullptr);
564 assert(F
.getTemplateSpecializationArgs()->size() > 0);
566 const int TemplateParamOptionalWrappersCount
=
567 countOptionalWrappers(*MatchRes
.Context
, F
.getTemplateSpecializationArgs()
570 .getNonReferenceType());
571 const int ArgTypeOptionalWrappersCount
= countOptionalWrappers(
572 *MatchRes
.Context
, E
.getType().getNonReferenceType());
574 // Check if this is a constructor/assignment call for `optional<T>` with
575 // argument of type `U` such that `T` is constructible from `U`.
576 if (TemplateParamOptionalWrappersCount
== ArgTypeOptionalWrappersCount
)
577 return State
.Env
.getBoolLiteralValue(true);
579 // This is a constructor/assignment call for `optional<T>` with argument of
580 // type `optional<U>` such that `T` is constructible from `U`.
581 if (auto *HasValueVal
= getHasValue(State
.Env
, State
.Env
.getValue(E
)))
583 return State
.Env
.makeAtomicBoolValue();
586 void transferValueOrConversionConstructor(
587 const CXXConstructExpr
*E
, const MatchFinder::MatchResult
&MatchRes
,
588 LatticeTransferState
&State
) {
589 assert(E
->getNumArgs() > 0);
591 constructOptionalValue(*E
, State
.Env
,
592 valueOrConversionHasValue(*E
->getConstructor(),
593 *E
->getArg(0), MatchRes
,
597 void transferAssignment(const CXXOperatorCallExpr
*E
, BoolValue
&HasValueVal
,
598 LatticeTransferState
&State
) {
599 assert(E
->getNumArgs() > 0);
601 if (auto *Loc
= cast_or_null
<RecordStorageLocation
>(
602 State
.Env
.getStorageLocation(*E
->getArg(0)))) {
603 createOptionalValue(*Loc
, HasValueVal
, State
.Env
);
605 // Assign a storage location for the whole expression.
606 State
.Env
.setStorageLocation(*E
, *Loc
);
610 void transferValueOrConversionAssignment(
611 const CXXOperatorCallExpr
*E
, const MatchFinder::MatchResult
&MatchRes
,
612 LatticeTransferState
&State
) {
613 assert(E
->getNumArgs() > 1);
614 transferAssignment(E
,
615 valueOrConversionHasValue(*E
->getDirectCallee(),
616 *E
->getArg(1), MatchRes
, State
),
620 void transferNulloptAssignment(const CXXOperatorCallExpr
*E
,
621 const MatchFinder::MatchResult
&,
622 LatticeTransferState
&State
) {
623 transferAssignment(E
, State
.Env
.getBoolLiteralValue(false), State
);
626 void transferSwap(RecordStorageLocation
*Loc1
, RecordStorageLocation
*Loc2
,
628 // We account for cases where one or both of the optionals are not modeled,
629 // either lacking associated storage locations, or lacking values associated
630 // to such storage locations.
632 if (Loc1
== nullptr) {
634 createOptionalValue(*Loc2
, Env
.makeAtomicBoolValue(), Env
);
637 if (Loc2
== nullptr) {
638 createOptionalValue(*Loc1
, Env
.makeAtomicBoolValue(), Env
);
642 // Both expressions have locations, though they may not have corresponding
643 // values. In that case, we create a fresh value at this point. Note that if
644 // two branches both do this, they will not share the value, but it at least
645 // allows for local reasoning about the value. To avoid the above, we would
646 // need *lazy* value allocation.
647 // FIXME: allocate values lazily, instead of just creating a fresh value.
648 BoolValue
*BoolVal1
= getHasValue(Env
, Env
.getValue(*Loc1
));
649 if (BoolVal1
== nullptr)
650 BoolVal1
= &Env
.makeAtomicBoolValue();
652 BoolValue
*BoolVal2
= getHasValue(Env
, Env
.getValue(*Loc2
));
653 if (BoolVal2
== nullptr)
654 BoolVal2
= &Env
.makeAtomicBoolValue();
656 createOptionalValue(*Loc1
, *BoolVal2
, Env
);
657 createOptionalValue(*Loc2
, *BoolVal1
, Env
);
660 void transferSwapCall(const CXXMemberCallExpr
*E
,
661 const MatchFinder::MatchResult
&,
662 LatticeTransferState
&State
) {
663 assert(E
->getNumArgs() == 1);
664 auto *OtherLoc
= cast_or_null
<RecordStorageLocation
>(
665 State
.Env
.getStorageLocation(*E
->getArg(0)));
666 transferSwap(getImplicitObjectLocation(*E
, State
.Env
), OtherLoc
, State
.Env
);
669 void transferStdSwapCall(const CallExpr
*E
, const MatchFinder::MatchResult
&,
670 LatticeTransferState
&State
) {
671 assert(E
->getNumArgs() == 2);
672 auto *Arg0Loc
= cast_or_null
<RecordStorageLocation
>(
673 State
.Env
.getStorageLocation(*E
->getArg(0)));
674 auto *Arg1Loc
= cast_or_null
<RecordStorageLocation
>(
675 State
.Env
.getStorageLocation(*E
->getArg(1)));
676 transferSwap(Arg0Loc
, Arg1Loc
, State
.Env
);
679 void transferStdForwardCall(const CallExpr
*E
, const MatchFinder::MatchResult
&,
680 LatticeTransferState
&State
) {
681 assert(E
->getNumArgs() == 1);
683 if (auto *Loc
= State
.Env
.getStorageLocation(*E
->getArg(0)))
684 State
.Env
.setStorageLocation(*E
, *Loc
);
687 const Formula
&evaluateEquality(Arena
&A
, const Formula
&EqVal
,
688 const Formula
&LHS
, const Formula
&RHS
) {
689 // Logically, an optional<T> object is composed of two values - a `has_value`
690 // bit and a value of type T. Equality of optional objects compares both
691 // values. Therefore, merely comparing the `has_value` bits isn't sufficient:
692 // when two optional objects are engaged, the equality of their respective
693 // values of type T matters. Since we only track the `has_value` bits, we
694 // can't make any conclusions about equality when we know that two optional
695 // objects are engaged.
697 // We express this as two facts about the equality:
698 // a) EqVal => (LHS & RHS) v (!RHS & !LHS)
699 // If they are equal, then either both are set or both are unset.
700 // b) (!LHS & !RHS) => EqVal
701 // If neither is set, then they are equal.
702 // We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
704 A
.makeImplies(EqVal
, A
.makeOr(A
.makeAnd(LHS
, RHS
),
705 A
.makeAnd(A
.makeNot(LHS
), A
.makeNot(RHS
)))),
706 A
.makeImplies(A
.makeNot(EqVal
), A
.makeOr(LHS
, RHS
)));
709 void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr
*CmpExpr
,
710 const MatchFinder::MatchResult
&,
711 LatticeTransferState
&State
) {
712 Environment
&Env
= State
.Env
;
713 auto &A
= Env
.arena();
714 auto *CmpValue
= &forceBoolValue(Env
, *CmpExpr
);
715 if (auto *LHasVal
= getHasValue(Env
, Env
.getValue(*CmpExpr
->getArg(0))))
716 if (auto *RHasVal
= getHasValue(Env
, Env
.getValue(*CmpExpr
->getArg(1)))) {
717 if (CmpExpr
->getOperator() == clang::OO_ExclaimEqual
)
718 CmpValue
= &A
.makeNot(*CmpValue
);
719 Env
.assume(evaluateEquality(A
, *CmpValue
, LHasVal
->formula(),
720 RHasVal
->formula()));
724 void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr
*CmpExpr
,
725 const clang::Expr
*E
, Environment
&Env
) {
726 auto &A
= Env
.arena();
727 auto *CmpValue
= &forceBoolValue(Env
, *CmpExpr
);
728 if (auto *HasVal
= getHasValue(Env
, Env
.getValue(*E
))) {
729 if (CmpExpr
->getOperator() == clang::OO_ExclaimEqual
)
730 CmpValue
= &A
.makeNot(*CmpValue
);
732 evaluateEquality(A
, *CmpValue
, HasVal
->formula(), A
.makeLiteral(true)));
736 std::optional
<StatementMatcher
>
737 ignorableOptional(const UncheckedOptionalAccessModelOptions
&Options
) {
738 if (Options
.IgnoreSmartPointerDereference
) {
739 auto SmartPtrUse
= expr(ignoringParenImpCasts(cxxOperatorCallExpr(
740 anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),
741 unless(hasArgument(0, expr(hasOptionalType()))))));
743 anyOf(SmartPtrUse
, memberExpr(hasObjectExpression(SmartPtrUse
))));
749 valueCall(const std::optional
<StatementMatcher
> &IgnorableOptional
) {
750 return isOptionalMemberCallWithNameMatcher(hasName("value"),
755 valueOperatorCall(const std::optional
<StatementMatcher
> &IgnorableOptional
) {
756 return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional
),
757 isOptionalOperatorCallWithName("->", IgnorableOptional
)));
760 auto buildTransferMatchSwitch() {
761 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a
762 // lot of duplicated work (e.g. string comparisons), consider providing APIs
763 // that avoid it through memoization.
764 return CFGMatchSwitchBuilder
<LatticeTransferState
>()
765 // Attach a symbolic "has_value" state to optional values that we see for
767 .CaseOfCFGStmt
<Expr
>(
768 expr(anyOf(declRefExpr(), memberExpr()), hasOptionalType()),
769 initializeOptionalReference
)
772 .CaseOfCFGStmt
<CallExpr
>(isMakeOptionalCall(), transferMakeOptionalCall
)
774 // optional::optional (in place)
775 .CaseOfCFGStmt
<CXXConstructExpr
>(
776 isOptionalInPlaceConstructor(),
777 [](const CXXConstructExpr
*E
, const MatchFinder::MatchResult
&,
778 LatticeTransferState
&State
) {
779 constructOptionalValue(*E
, State
.Env
,
780 State
.Env
.getBoolLiteralValue(true));
782 // nullopt_t::nullopt_t
783 .CaseOfCFGStmt
<CXXConstructExpr
>(
784 isNulloptConstructor(),
785 [](const CXXConstructExpr
*E
, const MatchFinder::MatchResult
&,
786 LatticeTransferState
&State
) {
787 constructOptionalValue(*E
, State
.Env
,
788 State
.Env
.getBoolLiteralValue(false));
790 // optional::optional(nullopt_t)
791 .CaseOfCFGStmt
<CXXConstructExpr
>(
792 isOptionalNulloptConstructor(),
793 [](const CXXConstructExpr
*E
, const MatchFinder::MatchResult
&,
794 LatticeTransferState
&State
) {
795 constructOptionalValue(*E
, State
.Env
,
796 State
.Env
.getBoolLiteralValue(false));
798 // optional::optional (value/conversion)
799 .CaseOfCFGStmt
<CXXConstructExpr
>(isOptionalValueOrConversionConstructor(),
800 transferValueOrConversionConstructor
)
802 // optional::operator=
803 .CaseOfCFGStmt
<CXXOperatorCallExpr
>(
804 isOptionalValueOrConversionAssignment(),
805 transferValueOrConversionAssignment
)
806 .CaseOfCFGStmt
<CXXOperatorCallExpr
>(isOptionalNulloptAssignment(),
807 transferNulloptAssignment
)
810 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
811 valueCall(std::nullopt
),
812 [](const CXXMemberCallExpr
*E
, const MatchFinder::MatchResult
&,
813 LatticeTransferState
&State
) {
814 transferUnwrapCall(E
, E
->getImplicitObjectArgument(), State
);
817 // optional::operator*
818 .CaseOfCFGStmt
<CallExpr
>(isOptionalOperatorCallWithName("*"),
819 [](const CallExpr
*E
,
820 const MatchFinder::MatchResult
&,
821 LatticeTransferState
&State
) {
822 transferUnwrapCall(E
, E
->getArg(0), State
);
825 // optional::operator->
826 .CaseOfCFGStmt
<CallExpr
>(isOptionalOperatorCallWithName("->"),
827 [](const CallExpr
*E
,
828 const MatchFinder::MatchResult
&,
829 LatticeTransferState
&State
) {
830 transferArrowOpCall(E
, E
->getArg(0), State
);
833 // optional::has_value, optional::hasValue
834 // Of the supported optionals only folly::Optional uses hasValue, but this
835 // will also pass for other types
836 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
837 isOptionalMemberCallWithNameMatcher(
838 hasAnyName("has_value", "hasValue")),
839 transferOptionalHasValueCall
)
841 // optional::operator bool
842 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
843 isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
844 transferOptionalHasValueCall
)
847 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
848 isOptionalMemberCallWithNameMatcher(hasName("emplace")),
849 [](const CXXMemberCallExpr
*E
, const MatchFinder::MatchResult
&,
850 LatticeTransferState
&State
) {
851 if (RecordStorageLocation
*Loc
=
852 getImplicitObjectLocation(*E
, State
.Env
)) {
853 createOptionalValue(*Loc
, State
.Env
.getBoolLiteralValue(true),
859 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
860 isOptionalMemberCallWithNameMatcher(hasName("reset")),
861 [](const CXXMemberCallExpr
*E
, const MatchFinder::MatchResult
&,
862 LatticeTransferState
&State
) {
863 if (RecordStorageLocation
*Loc
=
864 getImplicitObjectLocation(*E
, State
.Env
)) {
865 createOptionalValue(*Loc
, State
.Env
.getBoolLiteralValue(false),
871 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
872 isOptionalMemberCallWithNameMatcher(hasName("swap")),
876 .CaseOfCFGStmt
<CallExpr
>(isStdSwapCall(), transferStdSwapCall
)
879 .CaseOfCFGStmt
<CallExpr
>(isStdForwardCall(), transferStdForwardCall
)
881 // opt.value_or("").empty()
882 .CaseOfCFGStmt
<Expr
>(isValueOrStringEmptyCall(),
883 transferValueOrStringEmptyCall
)
885 // opt.value_or(X) != X
886 .CaseOfCFGStmt
<Expr
>(isValueOrNotEqX(), transferValueOrNotEqX
)
888 // Comparisons (==, !=):
889 .CaseOfCFGStmt
<CXXOperatorCallExpr
>(
890 isComparisonOperatorCall(hasAnyOptionalType(), hasAnyOptionalType()),
891 transferOptionalAndOptionalCmp
)
892 .CaseOfCFGStmt
<CXXOperatorCallExpr
>(
893 isComparisonOperatorCall(hasOptionalType(),
894 unless(hasAnyOptionalType())),
895 [](const clang::CXXOperatorCallExpr
*Cmp
,
896 const MatchFinder::MatchResult
&, LatticeTransferState
&State
) {
897 transferOptionalAndValueCmp(Cmp
, Cmp
->getArg(0), State
.Env
);
899 .CaseOfCFGStmt
<CXXOperatorCallExpr
>(
900 isComparisonOperatorCall(unless(hasAnyOptionalType()),
902 [](const clang::CXXOperatorCallExpr
*Cmp
,
903 const MatchFinder::MatchResult
&, LatticeTransferState
&State
) {
904 transferOptionalAndValueCmp(Cmp
, Cmp
->getArg(1), State
.Env
);
908 .CaseOfCFGStmt
<CallExpr
>(isCallReturningOptional(),
909 transferCallReturningOptional
)
914 llvm::SmallVector
<SourceLocation
> diagnoseUnwrapCall(const Expr
*ObjectExpr
,
915 const Environment
&Env
) {
916 if (auto *OptionalVal
= getValueBehindPossiblePointer(*ObjectExpr
, Env
)) {
917 auto *Prop
= OptionalVal
->getProperty("has_value");
918 if (auto *HasValueVal
= cast_or_null
<BoolValue
>(Prop
)) {
919 if (Env
.proves(HasValueVal
->formula()))
924 // Record that this unwrap is *not* provably safe.
925 // FIXME: include either the name of the optional (if applicable) or a source
926 // range of the access for easier interpretation of the result.
927 return {ObjectExpr
->getBeginLoc()};
930 auto buildDiagnoseMatchSwitch(
931 const UncheckedOptionalAccessModelOptions
&Options
) {
932 // FIXME: Evaluate the efficiency of matchers. If using matchers results in a
933 // lot of duplicated work (e.g. string comparisons), consider providing APIs
934 // that avoid it through memoization.
935 auto IgnorableOptional
= ignorableOptional(Options
);
936 return CFGMatchSwitchBuilder
<const Environment
,
937 llvm::SmallVector
<SourceLocation
>>()
939 .CaseOfCFGStmt
<CXXMemberCallExpr
>(
940 valueCall(IgnorableOptional
),
941 [](const CXXMemberCallExpr
*E
, const MatchFinder::MatchResult
&,
942 const Environment
&Env
) {
943 return diagnoseUnwrapCall(E
->getImplicitObjectArgument(), Env
);
946 // optional::operator*, optional::operator->
947 .CaseOfCFGStmt
<CallExpr
>(valueOperatorCall(IgnorableOptional
),
948 [](const CallExpr
*E
,
949 const MatchFinder::MatchResult
&,
950 const Environment
&Env
) {
951 return diagnoseUnwrapCall(E
->getArg(0), Env
);
958 ast_matchers::DeclarationMatcher
959 UncheckedOptionalAccessModel::optionalClassDecl() {
960 return optionalClass();
963 UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext
&Ctx
)
964 : DataflowAnalysis
<UncheckedOptionalAccessModel
, NoopLattice
>(Ctx
),
965 TransferMatchSwitch(buildTransferMatchSwitch()) {}
967 void UncheckedOptionalAccessModel::transfer(const CFGElement
&Elt
,
968 NoopLattice
&L
, Environment
&Env
) {
969 LatticeTransferState
State(L
, Env
);
970 TransferMatchSwitch(Elt
, getASTContext(), State
);
973 ComparisonResult
UncheckedOptionalAccessModel::compare(
974 QualType Type
, const Value
&Val1
, const Environment
&Env1
,
975 const Value
&Val2
, const Environment
&Env2
) {
976 if (!isOptionalType(Type
))
977 return ComparisonResult::Unknown
;
978 bool MustNonEmpty1
= isNonEmptyOptional(Val1
, Env1
);
979 bool MustNonEmpty2
= isNonEmptyOptional(Val2
, Env2
);
980 if (MustNonEmpty1
&& MustNonEmpty2
)
981 return ComparisonResult::Same
;
982 // If exactly one is true, then they're different, no reason to check whether
983 // they're definitely empty.
984 if (MustNonEmpty1
|| MustNonEmpty2
)
985 return ComparisonResult::Different
;
986 // Check if they're both definitely empty.
987 return (isEmptyOptional(Val1
, Env1
) && isEmptyOptional(Val2
, Env2
))
988 ? ComparisonResult::Same
989 : ComparisonResult::Different
;
992 bool UncheckedOptionalAccessModel::merge(QualType Type
, const Value
&Val1
,
993 const Environment
&Env1
,
995 const Environment
&Env2
,
997 Environment
&MergedEnv
) {
998 if (!isOptionalType(Type
))
1000 // FIXME: uses same approach as join for `BoolValues`. Requires non-const
1001 // values, though, so will require updating the interface.
1002 auto &HasValueVal
= MergedEnv
.makeAtomicBoolValue();
1003 bool MustNonEmpty1
= isNonEmptyOptional(Val1
, Env1
);
1004 bool MustNonEmpty2
= isNonEmptyOptional(Val2
, Env2
);
1005 if (MustNonEmpty1
&& MustNonEmpty2
)
1006 MergedEnv
.assume(HasValueVal
.formula());
1008 // Only make the costly calls to `isEmptyOptional` if we got "unknown"
1009 // (false) for both calls to `isNonEmptyOptional`.
1010 !MustNonEmpty1
&& !MustNonEmpty2
&& isEmptyOptional(Val1
, Env1
) &&
1011 isEmptyOptional(Val2
, Env2
))
1012 MergedEnv
.assume(MergedEnv
.arena().makeNot(HasValueVal
.formula()));
1013 setHasValue(MergedVal
, HasValueVal
);
1017 Value
*UncheckedOptionalAccessModel::widen(QualType Type
, Value
&Prev
,
1018 const Environment
&PrevEnv
,
1020 Environment
&CurrentEnv
) {
1021 switch (compare(Type
, Prev
, PrevEnv
, Current
, CurrentEnv
)) {
1022 case ComparisonResult::Same
:
1024 case ComparisonResult::Different
:
1025 if (auto *PrevHasVal
=
1026 cast_or_null
<BoolValue
>(Prev
.getProperty("has_value"))) {
1027 if (isa
<TopBoolValue
>(PrevHasVal
))
1030 if (auto *CurrentHasVal
=
1031 cast_or_null
<BoolValue
>(Current
.getProperty("has_value"))) {
1032 if (isa
<TopBoolValue
>(CurrentHasVal
))
1035 return &createOptionalValue(cast
<RecordValue
>(Current
).getLoc(),
1036 CurrentEnv
.makeTopBoolValue(), CurrentEnv
);
1037 case ComparisonResult::Unknown
:
1040 llvm_unreachable("all cases covered in switch");
1043 UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(
1044 UncheckedOptionalAccessModelOptions Options
)
1045 : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options
)) {}
1047 } // namespace dataflow
1048 } // namespace clang