Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/llvm-project/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
35323 views
1
//===-- UncheckedOptionalAccessModel.cpp ------------------------*- C++ -*-===//
2
//
3
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4
// See https://llvm.org/LICENSE.txt for license information.
5
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6
//
7
//===----------------------------------------------------------------------===//
8
//
9
// This file defines a dataflow analysis that detects unsafe uses of optional
10
// values.
11
//
12
//===----------------------------------------------------------------------===//
13
14
#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
15
#include "clang/AST/ASTContext.h"
16
#include "clang/AST/DeclCXX.h"
17
#include "clang/AST/Expr.h"
18
#include "clang/AST/ExprCXX.h"
19
#include "clang/AST/Stmt.h"
20
#include "clang/ASTMatchers/ASTMatchers.h"
21
#include "clang/ASTMatchers/ASTMatchersMacros.h"
22
#include "clang/Analysis/CFG.h"
23
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
24
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
25
#include "clang/Analysis/FlowSensitive/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"
33
#include <cassert>
34
#include <memory>
35
#include <optional>
36
#include <utility>
37
38
namespace clang {
39
namespace dataflow {
40
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();
45
}
46
47
static bool hasOptionalClassName(const CXXRecordDecl &RD) {
48
if (!RD.getDeclName().isIdentifier())
49
return false;
50
51
if (RD.getName() == "optional") {
52
if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))
53
return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl");
54
return false;
55
}
56
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"));
62
}
63
64
return false;
65
}
66
67
static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) {
68
if (RD == nullptr)
69
return nullptr;
70
if (hasOptionalClassName(*RD))
71
return RD;
72
73
if (!RD->hasDefinition())
74
return nullptr;
75
76
for (const CXXBaseSpecifier &Base : RD->bases())
77
if (const CXXRecordDecl *BaseClass =
78
getOptionalBaseClass(Base.getType()->getAsCXXRecordDecl()))
79
return BaseClass;
80
81
return nullptr;
82
}
83
84
namespace {
85
86
using namespace ::clang::ast_matchers;
87
using LatticeTransferState = TransferState<NoopLattice>;
88
89
AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(Node); }
90
91
AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {
92
return getOptionalBaseClass(&Node) != nullptr;
93
}
94
95
auto desugarsToOptionalType() {
96
return hasUnqualifiedDesugaredType(
97
recordType(hasDeclaration(cxxRecordDecl(optionalClass()))));
98
}
99
100
auto desugarsToOptionalOrDerivedType() {
101
return hasUnqualifiedDesugaredType(
102
recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));
103
}
104
105
auto hasOptionalType() { return hasType(desugarsToOptionalType()); }
106
107
/// Matches any of the spellings of the optional types and sugar, aliases,
108
/// derived classes, etc.
109
auto hasOptionalOrDerivedType() {
110
return hasType(desugarsToOptionalOrDerivedType());
111
}
112
113
QualType getPublicType(const Expr *E) {
114
auto *Cast = dyn_cast<ImplicitCastExpr>(E->IgnoreParens());
115
if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase) {
116
QualType Ty = E->getType();
117
if (Ty->isPointerType())
118
return Ty->getPointeeType();
119
return Ty;
120
}
121
122
// Is the derived type that we're casting from the type of `*this`? In this
123
// special case, we can upcast to the base class even if the base is
124
// non-public.
125
bool CastingFromThis = isa<CXXThisExpr>(Cast->getSubExpr());
126
127
// Find the least-derived type in the path (i.e. the last entry in the list)
128
// that we can access.
129
const CXXBaseSpecifier *PublicBase = nullptr;
130
for (const CXXBaseSpecifier *Base : Cast->path()) {
131
if (Base->getAccessSpecifier() != AS_public && !CastingFromThis)
132
break;
133
PublicBase = Base;
134
CastingFromThis = false;
135
}
136
137
if (PublicBase != nullptr)
138
return PublicBase->getType();
139
140
// We didn't find any public type that we could cast to. There may be more
141
// casts in `getSubExpr()`, so recurse. (If there aren't any more casts, this
142
// will return the type of `getSubExpr()`.)
143
return getPublicType(Cast->getSubExpr());
144
}
145
146
// Returns the least-derived type for the receiver of `MCE` that
147
// `MCE.getImplicitObjectArgument()->IgnoreParentImpCasts()` can be downcast to.
148
// Effectively, we upcast until we reach a non-public base class, unless that
149
// base is a base of `*this`.
150
//
151
// This is needed to correctly match methods called on types derived from
152
// `std::optional`.
153
//
154
// Say we have a `struct Derived : public std::optional<int> {} d;` For a call
155
// `d.has_value()`, the `getImplicitObjectArgument()` looks like this:
156
//
157
// ImplicitCastExpr 'const std::__optional_storage_base<int>' lvalue
158
// | <UncheckedDerivedToBase (optional -> __optional_storage_base)>
159
// `-DeclRefExpr 'Derived' lvalue Var 'd' 'Derived'
160
//
161
// The type of the implicit object argument is `__optional_storage_base`
162
// (since this is the internal type that `has_value()` is declared on). If we
163
// call `IgnoreParenImpCasts()` on the implicit object argument, we get the
164
// `DeclRefExpr`, which has type `Derived`. Neither of these types is
165
// `optional`, and hence neither is sufficient for querying whether we are
166
// calling a method on `optional`.
167
//
168
// Instead, starting with the most derived type, we need to follow the chain of
169
// casts
170
QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) {
171
return getPublicType(MCE.getImplicitObjectArgument());
172
}
173
174
AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType,
175
ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
176
return InnerMatcher.matches(getPublicReceiverType(Node), Finder, Builder);
177
}
178
179
auto isOptionalMemberCallWithNameMatcher(
180
ast_matchers::internal::Matcher<NamedDecl> matcher,
181
const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
182
return cxxMemberCallExpr(Ignorable ? on(expr(unless(*Ignorable)))
183
: anything(),
184
publicReceiverType(desugarsToOptionalType()),
185
callee(cxxMethodDecl(matcher)));
186
}
187
188
auto isOptionalOperatorCallWithName(
189
llvm::StringRef operator_name,
190
const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
191
return cxxOperatorCallExpr(
192
hasOverloadedOperatorName(operator_name),
193
callee(cxxMethodDecl(ofClass(optionalClass()))),
194
Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());
195
}
196
197
auto isMakeOptionalCall() {
198
return callExpr(callee(functionDecl(hasAnyName(
199
"std::make_optional", "base::make_optional",
200
"absl::make_optional", "folly::make_optional"))),
201
hasOptionalType());
202
}
203
204
auto nulloptTypeDecl() {
205
return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",
206
"base::nullopt_t", "folly::None"));
207
}
208
209
auto hasNulloptType() { return hasType(nulloptTypeDecl()); }
210
211
auto inPlaceClass() {
212
return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",
213
"base::in_place_t", "folly::in_place_t"));
214
}
215
216
auto isOptionalNulloptConstructor() {
217
return cxxConstructExpr(
218
hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
219
hasParameter(0, hasNulloptType()))),
220
hasOptionalOrDerivedType());
221
}
222
223
auto isOptionalInPlaceConstructor() {
224
return cxxConstructExpr(hasArgument(0, hasType(inPlaceClass())),
225
hasOptionalOrDerivedType());
226
}
227
228
auto isOptionalValueOrConversionConstructor() {
229
return cxxConstructExpr(
230
unless(hasDeclaration(
231
cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
232
argumentCountIs(1), hasArgument(0, unless(hasNulloptType())),
233
hasOptionalOrDerivedType());
234
}
235
236
auto isOptionalValueOrConversionAssignment() {
237
return cxxOperatorCallExpr(
238
hasOverloadedOperatorName("="),
239
callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
240
unless(hasDeclaration(cxxMethodDecl(
241
anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
242
argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
243
}
244
245
auto isOptionalNulloptAssignment() {
246
return cxxOperatorCallExpr(
247
hasOverloadedOperatorName("="),
248
callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
249
argumentCountIs(2), hasArgument(1, hasNulloptType()));
250
}
251
252
auto isStdSwapCall() {
253
return callExpr(callee(functionDecl(hasName("std::swap"))),
254
argumentCountIs(2),
255
hasArgument(0, hasOptionalOrDerivedType()),
256
hasArgument(1, hasOptionalOrDerivedType()));
257
}
258
259
auto isStdForwardCall() {
260
return callExpr(callee(functionDecl(hasName("std::forward"))),
261
argumentCountIs(1),
262
hasArgument(0, hasOptionalOrDerivedType()));
263
}
264
265
constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
266
267
auto isValueOrStringEmptyCall() {
268
// `opt.value_or("").empty()`
269
return cxxMemberCallExpr(
270
callee(cxxMethodDecl(hasName("empty"))),
271
onImplicitObjectArgument(ignoringImplicit(
272
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
273
callee(cxxMethodDecl(hasName("value_or"),
274
ofClass(optionalClass()))),
275
hasArgument(0, stringLiteral(hasSize(0))))
276
.bind(ValueOrCallID))));
277
}
278
279
auto isValueOrNotEqX() {
280
auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {
281
return hasOperands(
282
ignoringImplicit(
283
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
284
callee(cxxMethodDecl(hasName("value_or"),
285
ofClass(optionalClass()))),
286
hasArgument(0, Arg))
287
.bind(ValueOrCallID)),
288
ignoringImplicit(Arg));
289
};
290
291
// `opt.value_or(X) != X`, for X is `nullptr`, `""`, or `0`. Ideally, we'd
292
// support this pattern for any expression, but the AST does not have a
293
// generic expression comparison facility, so we specialize to common cases
294
// seen in practice. FIXME: define a matcher that compares values across
295
// nodes, which would let us generalize this to any `X`.
296
return binaryOperation(hasOperatorName("!="),
297
anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
298
ComparesToSame(stringLiteral(hasSize(0))),
299
ComparesToSame(integerLiteral(equals(0)))));
300
}
301
302
auto isCallReturningOptional() {
303
return callExpr(hasType(qualType(
304
anyOf(desugarsToOptionalOrDerivedType(),
305
referenceType(pointee(desugarsToOptionalOrDerivedType()))))));
306
}
307
308
template <typename L, typename R>
309
auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) {
310
return cxxOperatorCallExpr(
311
anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),
312
argumentCountIs(2), hasArgument(0, lhs_arg_matcher),
313
hasArgument(1, rhs_arg_matcher));
314
}
315
316
/// Ensures that `Expr` is mapped to a `BoolValue` and returns its formula.
317
const Formula &forceBoolValue(Environment &Env, const Expr &Expr) {
318
auto *Value = Env.get<BoolValue>(Expr);
319
if (Value != nullptr)
320
return Value->formula();
321
322
Value = &Env.makeAtomicBoolValue();
323
Env.setValue(Expr, *Value);
324
return Value->formula();
325
}
326
327
StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {
328
return OptionalLoc.getSyntheticField("has_value");
329
}
330
331
StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) {
332
return OptionalLoc.getSyntheticField("value");
333
}
334
335
/// Sets `HasValueVal` as the symbolic value that represents the "has_value"
336
/// property of the optional at `OptionalLoc`.
337
void setHasValue(RecordStorageLocation &OptionalLoc, BoolValue &HasValueVal,
338
Environment &Env) {
339
Env.setValue(locForHasValue(OptionalLoc), HasValueVal);
340
}
341
342
/// Returns the symbolic value that represents the "has_value" property of the
343
/// optional at `OptionalLoc`. Returns null if `OptionalLoc` is null.
344
BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) {
345
if (OptionalLoc == nullptr)
346
return nullptr;
347
StorageLocation &HasValueLoc = locForHasValue(*OptionalLoc);
348
auto *HasValueVal = Env.get<BoolValue>(HasValueLoc);
349
if (HasValueVal == nullptr) {
350
HasValueVal = &Env.makeAtomicBoolValue();
351
Env.setValue(HasValueLoc, *HasValueVal);
352
}
353
return HasValueVal;
354
}
355
356
QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) {
357
auto &CTSD = cast<ClassTemplateSpecializationDecl>(RD);
358
return CTSD.getTemplateArgs()[0].getAsType();
359
}
360
361
/// Returns the number of optional wrappers in `Type`.
362
///
363
/// For example, if `Type` is `optional<optional<int>>`, the result of this
364
/// function will be 2.
365
int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
366
const CXXRecordDecl *Optional =
367
getOptionalBaseClass(Type->getAsCXXRecordDecl());
368
if (Optional == nullptr)
369
return 0;
370
return 1 + countOptionalWrappers(
371
ASTCtx,
372
valueTypeFromOptionalDecl(*Optional).getDesugaredType(ASTCtx));
373
}
374
375
StorageLocation *getLocBehindPossiblePointer(const Expr &E,
376
const Environment &Env) {
377
if (E.isPRValue()) {
378
if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Env.getValue(E)))
379
return &PointerVal->getPointeeLoc();
380
return nullptr;
381
}
382
return Env.getStorageLocation(E);
383
}
384
385
void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
386
LatticeTransferState &State) {
387
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
388
getLocBehindPossiblePointer(*ObjectExpr, State.Env))) {
389
if (State.Env.getStorageLocation(*UnwrapExpr) == nullptr)
390
State.Env.setStorageLocation(*UnwrapExpr, locForValue(*OptionalLoc));
391
}
392
}
393
394
void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
395
LatticeTransferState &State) {
396
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
397
getLocBehindPossiblePointer(*ObjectExpr, State.Env)))
398
State.Env.setValue(
399
*UnwrapExpr, State.Env.create<PointerValue>(locForValue(*OptionalLoc)));
400
}
401
402
void transferMakeOptionalCall(const CallExpr *E,
403
const MatchFinder::MatchResult &,
404
LatticeTransferState &State) {
405
setHasValue(State.Env.getResultObjectLocation(*E),
406
State.Env.getBoolLiteralValue(true), State.Env);
407
}
408
409
void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
410
const MatchFinder::MatchResult &,
411
LatticeTransferState &State) {
412
if (auto *HasValueVal = getHasValue(
413
State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {
414
State.Env.setValue(*CallExpr, *HasValueVal);
415
}
416
}
417
418
/// `ModelPred` builds a logical formula relating the predicate in
419
/// `ValueOrPredExpr` to the optional's `has_value` property.
420
void transferValueOrImpl(
421
const clang::Expr *ValueOrPredExpr, const MatchFinder::MatchResult &Result,
422
LatticeTransferState &State,
423
const Formula &(*ModelPred)(Environment &Env, const Formula &ExprVal,
424
const Formula &HasValueVal)) {
425
auto &Env = State.Env;
426
427
const auto *MCE =
428
Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID);
429
430
auto *HasValueVal =
431
getHasValue(State.Env, getImplicitObjectLocation(*MCE, State.Env));
432
if (HasValueVal == nullptr)
433
return;
434
435
Env.assume(ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr),
436
HasValueVal->formula()));
437
}
438
439
void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
440
const MatchFinder::MatchResult &Result,
441
LatticeTransferState &State) {
442
return transferValueOrImpl(ComparisonExpr, Result, State,
443
[](Environment &Env, const Formula &ExprVal,
444
const Formula &HasValueVal) -> const Formula & {
445
auto &A = Env.arena();
446
// If the result is *not* empty, then we know the
447
// optional must have been holding a value. If
448
// `ExprVal` is true, though, we don't learn
449
// anything definite about `has_value`, so we
450
// don't add any corresponding implications to
451
// the flow condition.
452
return A.makeImplies(A.makeNot(ExprVal),
453
HasValueVal);
454
});
455
}
456
457
void transferValueOrNotEqX(const Expr *ComparisonExpr,
458
const MatchFinder::MatchResult &Result,
459
LatticeTransferState &State) {
460
transferValueOrImpl(ComparisonExpr, Result, State,
461
[](Environment &Env, const Formula &ExprVal,
462
const Formula &HasValueVal) -> const Formula & {
463
auto &A = Env.arena();
464
// We know that if `(opt.value_or(X) != X)` then
465
// `opt.hasValue()`, even without knowing further
466
// details about the contents of `opt`.
467
return A.makeImplies(ExprVal, HasValueVal);
468
});
469
}
470
471
void transferCallReturningOptional(const CallExpr *E,
472
const MatchFinder::MatchResult &Result,
473
LatticeTransferState &State) {
474
RecordStorageLocation *Loc = nullptr;
475
if (E->isPRValue()) {
476
Loc = &State.Env.getResultObjectLocation(*E);
477
} else {
478
Loc = State.Env.get<RecordStorageLocation>(*E);
479
if (Loc == nullptr) {
480
Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));
481
State.Env.setStorageLocation(*E, *Loc);
482
}
483
}
484
485
if (State.Env.getValue(locForHasValue(*Loc)) != nullptr)
486
return;
487
488
setHasValue(*Loc, State.Env.makeAtomicBoolValue(), State.Env);
489
}
490
491
void constructOptionalValue(const Expr &E, Environment &Env,
492
BoolValue &HasValueVal) {
493
RecordStorageLocation &Loc = Env.getResultObjectLocation(E);
494
setHasValue(Loc, HasValueVal, Env);
495
}
496
497
/// Returns a symbolic value for the "has_value" property of an `optional<T>`
498
/// value that is constructed/assigned from a value of type `U` or `optional<U>`
499
/// where `T` is constructible from `U`.
500
BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E,
501
const MatchFinder::MatchResult &MatchRes,
502
LatticeTransferState &State) {
503
const int DestTypeOptionalWrappersCount =
504
countOptionalWrappers(*MatchRes.Context, DestType);
505
const int ArgTypeOptionalWrappersCount = countOptionalWrappers(
506
*MatchRes.Context, E.getType().getNonReferenceType());
507
508
// Is this an constructor of the form `template<class U> optional(U &&)` /
509
// assignment of the form `template<class U> optional& operator=(U &&)`
510
// (where `T` is assignable / constructible from `U`)?
511
// We recognize this because the number of optionals in the optional being
512
// assigned to is different from the function argument type.
513
if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount)
514
return State.Env.getBoolLiteralValue(true);
515
516
// Otherwise, this must be a constructor of the form
517
// `template <class U> optional<optional<U> &&)` / assignment of the form
518
// `template <class U> optional& operator=(optional<U> &&)
519
// (where, again, `T` is assignable / constructible from `U`).
520
auto *Loc = State.Env.get<RecordStorageLocation>(E);
521
if (auto *HasValueVal = getHasValue(State.Env, Loc))
522
return *HasValueVal;
523
return State.Env.makeAtomicBoolValue();
524
}
525
526
void transferValueOrConversionConstructor(
527
const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,
528
LatticeTransferState &State) {
529
assert(E->getNumArgs() > 0);
530
531
constructOptionalValue(
532
*E, State.Env,
533
valueOrConversionHasValue(
534
E->getConstructor()->getThisType()->getPointeeType(), *E->getArg(0),
535
MatchRes, State));
536
}
537
538
void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,
539
LatticeTransferState &State) {
540
assert(E->getNumArgs() > 0);
541
542
if (auto *Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0))) {
543
setHasValue(*Loc, HasValueVal, State.Env);
544
545
// Assign a storage location for the whole expression.
546
State.Env.setStorageLocation(*E, *Loc);
547
}
548
}
549
550
void transferValueOrConversionAssignment(
551
const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,
552
LatticeTransferState &State) {
553
assert(E->getNumArgs() > 1);
554
transferAssignment(
555
E,
556
valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),
557
*E->getArg(1), MatchRes, State),
558
State);
559
}
560
561
void transferNulloptAssignment(const CXXOperatorCallExpr *E,
562
const MatchFinder::MatchResult &,
563
LatticeTransferState &State) {
564
transferAssignment(E, State.Env.getBoolLiteralValue(false), State);
565
}
566
567
void transferSwap(RecordStorageLocation *Loc1, RecordStorageLocation *Loc2,
568
Environment &Env) {
569
// We account for cases where one or both of the optionals are not modeled,
570
// either lacking associated storage locations, or lacking values associated
571
// to such storage locations.
572
573
if (Loc1 == nullptr) {
574
if (Loc2 != nullptr)
575
setHasValue(*Loc2, Env.makeAtomicBoolValue(), Env);
576
return;
577
}
578
if (Loc2 == nullptr) {
579
setHasValue(*Loc1, Env.makeAtomicBoolValue(), Env);
580
return;
581
}
582
583
// Both expressions have locations, though they may not have corresponding
584
// values. In that case, we create a fresh value at this point. Note that if
585
// two branches both do this, they will not share the value, but it at least
586
// allows for local reasoning about the value. To avoid the above, we would
587
// need *lazy* value allocation.
588
// FIXME: allocate values lazily, instead of just creating a fresh value.
589
BoolValue *BoolVal1 = getHasValue(Env, Loc1);
590
if (BoolVal1 == nullptr)
591
BoolVal1 = &Env.makeAtomicBoolValue();
592
593
BoolValue *BoolVal2 = getHasValue(Env, Loc2);
594
if (BoolVal2 == nullptr)
595
BoolVal2 = &Env.makeAtomicBoolValue();
596
597
setHasValue(*Loc1, *BoolVal2, Env);
598
setHasValue(*Loc2, *BoolVal1, Env);
599
}
600
601
void transferSwapCall(const CXXMemberCallExpr *E,
602
const MatchFinder::MatchResult &,
603
LatticeTransferState &State) {
604
assert(E->getNumArgs() == 1);
605
auto *OtherLoc = State.Env.get<RecordStorageLocation>(*E->getArg(0));
606
transferSwap(getImplicitObjectLocation(*E, State.Env), OtherLoc, State.Env);
607
}
608
609
void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
610
LatticeTransferState &State) {
611
assert(E->getNumArgs() == 2);
612
auto *Arg0Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0));
613
auto *Arg1Loc = State.Env.get<RecordStorageLocation>(*E->getArg(1));
614
transferSwap(Arg0Loc, Arg1Loc, State.Env);
615
}
616
617
void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &,
618
LatticeTransferState &State) {
619
assert(E->getNumArgs() == 1);
620
621
if (auto *Loc = State.Env.getStorageLocation(*E->getArg(0)))
622
State.Env.setStorageLocation(*E, *Loc);
623
}
624
625
const Formula &evaluateEquality(Arena &A, const Formula &EqVal,
626
const Formula &LHS, const Formula &RHS) {
627
// Logically, an optional<T> object is composed of two values - a `has_value`
628
// bit and a value of type T. Equality of optional objects compares both
629
// values. Therefore, merely comparing the `has_value` bits isn't sufficient:
630
// when two optional objects are engaged, the equality of their respective
631
// values of type T matters. Since we only track the `has_value` bits, we
632
// can't make any conclusions about equality when we know that two optional
633
// objects are engaged.
634
//
635
// We express this as two facts about the equality:
636
// a) EqVal => (LHS & RHS) v (!RHS & !LHS)
637
// If they are equal, then either both are set or both are unset.
638
// b) (!LHS & !RHS) => EqVal
639
// If neither is set, then they are equal.
640
// We rewrite b) as !EqVal => (LHS v RHS), for a more compact formula.
641
return A.makeAnd(
642
A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),
643
A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),
644
A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));
645
}
646
647
void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,
648
const MatchFinder::MatchResult &,
649
LatticeTransferState &State) {
650
Environment &Env = State.Env;
651
auto &A = Env.arena();
652
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
653
auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0));
654
if (auto *LHasVal = getHasValue(Env, Arg0Loc)) {
655
auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1));
656
if (auto *RHasVal = getHasValue(Env, Arg1Loc)) {
657
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
658
CmpValue = &A.makeNot(*CmpValue);
659
Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(),
660
RHasVal->formula()));
661
}
662
}
663
}
664
665
void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,
666
const clang::Expr *E, Environment &Env) {
667
auto &A = Env.arena();
668
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
669
auto *Loc = Env.get<RecordStorageLocation>(*E);
670
if (auto *HasVal = getHasValue(Env, Loc)) {
671
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
672
CmpValue = &A.makeNot(*CmpValue);
673
Env.assume(
674
evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(true)));
675
}
676
}
677
678
void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr,
679
const clang::Expr *E, Environment &Env) {
680
auto &A = Env.arena();
681
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
682
auto *Loc = Env.get<RecordStorageLocation>(*E);
683
if (auto *HasVal = getHasValue(Env, Loc)) {
684
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
685
CmpValue = &A.makeNot(*CmpValue);
686
Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(),
687
A.makeLiteral(false)));
688
}
689
}
690
691
std::optional<StatementMatcher>
692
ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
693
if (Options.IgnoreSmartPointerDereference) {
694
auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(
695
anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),
696
unless(hasArgument(0, expr(hasOptionalType()))))));
697
return expr(
698
anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));
699
}
700
return std::nullopt;
701
}
702
703
StatementMatcher
704
valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
705
return isOptionalMemberCallWithNameMatcher(hasName("value"),
706
IgnorableOptional);
707
}
708
709
StatementMatcher
710
valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {
711
return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),
712
isOptionalOperatorCallWithName("->", IgnorableOptional)));
713
}
714
715
auto buildTransferMatchSwitch() {
716
// FIXME: Evaluate the efficiency of matchers. If using matchers results in a
717
// lot of duplicated work (e.g. string comparisons), consider providing APIs
718
// that avoid it through memoization.
719
return CFGMatchSwitchBuilder<LatticeTransferState>()
720
// make_optional
721
.CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)
722
723
// optional::optional (in place)
724
.CaseOfCFGStmt<CXXConstructExpr>(
725
isOptionalInPlaceConstructor(),
726
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
727
LatticeTransferState &State) {
728
constructOptionalValue(*E, State.Env,
729
State.Env.getBoolLiteralValue(true));
730
})
731
// optional::optional(nullopt_t)
732
.CaseOfCFGStmt<CXXConstructExpr>(
733
isOptionalNulloptConstructor(),
734
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
735
LatticeTransferState &State) {
736
constructOptionalValue(*E, State.Env,
737
State.Env.getBoolLiteralValue(false));
738
})
739
// optional::optional (value/conversion)
740
.CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
741
transferValueOrConversionConstructor)
742
743
// optional::operator=
744
.CaseOfCFGStmt<CXXOperatorCallExpr>(
745
isOptionalValueOrConversionAssignment(),
746
transferValueOrConversionAssignment)
747
.CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),
748
transferNulloptAssignment)
749
750
// optional::value
751
.CaseOfCFGStmt<CXXMemberCallExpr>(
752
valueCall(std::nullopt),
753
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
754
LatticeTransferState &State) {
755
transferUnwrapCall(E, E->getImplicitObjectArgument(), State);
756
})
757
758
// optional::operator*
759
.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"),
760
[](const CallExpr *E,
761
const MatchFinder::MatchResult &,
762
LatticeTransferState &State) {
763
transferUnwrapCall(E, E->getArg(0), State);
764
})
765
766
// optional::operator->
767
.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"),
768
[](const CallExpr *E,
769
const MatchFinder::MatchResult &,
770
LatticeTransferState &State) {
771
transferArrowOpCall(E, E->getArg(0), State);
772
})
773
774
// optional::has_value, optional::hasValue
775
// Of the supported optionals only folly::Optional uses hasValue, but this
776
// will also pass for other types
777
.CaseOfCFGStmt<CXXMemberCallExpr>(
778
isOptionalMemberCallWithNameMatcher(
779
hasAnyName("has_value", "hasValue")),
780
transferOptionalHasValueCall)
781
782
// optional::operator bool
783
.CaseOfCFGStmt<CXXMemberCallExpr>(
784
isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
785
transferOptionalHasValueCall)
786
787
// optional::emplace
788
.CaseOfCFGStmt<CXXMemberCallExpr>(
789
isOptionalMemberCallWithNameMatcher(hasName("emplace")),
790
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
791
LatticeTransferState &State) {
792
if (RecordStorageLocation *Loc =
793
getImplicitObjectLocation(*E, State.Env)) {
794
setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);
795
}
796
})
797
798
// optional::reset
799
.CaseOfCFGStmt<CXXMemberCallExpr>(
800
isOptionalMemberCallWithNameMatcher(hasName("reset")),
801
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
802
LatticeTransferState &State) {
803
if (RecordStorageLocation *Loc =
804
getImplicitObjectLocation(*E, State.Env)) {
805
setHasValue(*Loc, State.Env.getBoolLiteralValue(false),
806
State.Env);
807
}
808
})
809
810
// optional::swap
811
.CaseOfCFGStmt<CXXMemberCallExpr>(
812
isOptionalMemberCallWithNameMatcher(hasName("swap")),
813
transferSwapCall)
814
815
// std::swap
816
.CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall)
817
818
// std::forward
819
.CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall)
820
821
// opt.value_or("").empty()
822
.CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(),
823
transferValueOrStringEmptyCall)
824
825
// opt.value_or(X) != X
826
.CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
827
828
// Comparisons (==, !=):
829
.CaseOfCFGStmt<CXXOperatorCallExpr>(
830
isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),
831
transferOptionalAndOptionalCmp)
832
.CaseOfCFGStmt<CXXOperatorCallExpr>(
833
isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),
834
[](const clang::CXXOperatorCallExpr *Cmp,
835
const MatchFinder::MatchResult &, LatticeTransferState &State) {
836
transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);
837
})
838
.CaseOfCFGStmt<CXXOperatorCallExpr>(
839
isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),
840
[](const clang::CXXOperatorCallExpr *Cmp,
841
const MatchFinder::MatchResult &, LatticeTransferState &State) {
842
transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);
843
})
844
.CaseOfCFGStmt<CXXOperatorCallExpr>(
845
isComparisonOperatorCall(
846
hasOptionalType(),
847
unless(anyOf(hasOptionalType(), hasNulloptType()))),
848
[](const clang::CXXOperatorCallExpr *Cmp,
849
const MatchFinder::MatchResult &, LatticeTransferState &State) {
850
transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);
851
})
852
.CaseOfCFGStmt<CXXOperatorCallExpr>(
853
isComparisonOperatorCall(
854
unless(anyOf(hasOptionalType(), hasNulloptType())),
855
hasOptionalType()),
856
[](const clang::CXXOperatorCallExpr *Cmp,
857
const MatchFinder::MatchResult &, LatticeTransferState &State) {
858
transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);
859
})
860
861
// returns optional
862
.CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),
863
transferCallReturningOptional)
864
865
.Build();
866
}
867
868
llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
869
const Environment &Env) {
870
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
871
getLocBehindPossiblePointer(*ObjectExpr, Env))) {
872
auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
873
if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
874
if (Env.proves(HasValueVal->formula()))
875
return {};
876
}
877
}
878
879
// Record that this unwrap is *not* provably safe.
880
// FIXME: include either the name of the optional (if applicable) or a source
881
// range of the access for easier interpretation of the result.
882
return {ObjectExpr->getBeginLoc()};
883
}
884
885
auto buildDiagnoseMatchSwitch(
886
const UncheckedOptionalAccessModelOptions &Options) {
887
// FIXME: Evaluate the efficiency of matchers. If using matchers results in a
888
// lot of duplicated work (e.g. string comparisons), consider providing APIs
889
// that avoid it through memoization.
890
auto IgnorableOptional = ignorableOptional(Options);
891
return CFGMatchSwitchBuilder<const Environment,
892
llvm::SmallVector<SourceLocation>>()
893
// optional::value
894
.CaseOfCFGStmt<CXXMemberCallExpr>(
895
valueCall(IgnorableOptional),
896
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
897
const Environment &Env) {
898
return diagnoseUnwrapCall(E->getImplicitObjectArgument(), Env);
899
})
900
901
// optional::operator*, optional::operator->
902
.CaseOfCFGStmt<CallExpr>(valueOperatorCall(IgnorableOptional),
903
[](const CallExpr *E,
904
const MatchFinder::MatchResult &,
905
const Environment &Env) {
906
return diagnoseUnwrapCall(E->getArg(0), Env);
907
})
908
.Build();
909
}
910
911
} // namespace
912
913
ast_matchers::DeclarationMatcher
914
UncheckedOptionalAccessModel::optionalClassDecl() {
915
return cxxRecordDecl(optionalClass());
916
}
917
918
UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,
919
Environment &Env)
920
: DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx),
921
TransferMatchSwitch(buildTransferMatchSwitch()) {
922
Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
923
[&Ctx](QualType Ty) -> llvm::StringMap<QualType> {
924
const CXXRecordDecl *Optional =
925
getOptionalBaseClass(Ty->getAsCXXRecordDecl());
926
if (Optional == nullptr)
927
return {};
928
return {{"value", valueTypeFromOptionalDecl(*Optional)},
929
{"has_value", Ctx.BoolTy}};
930
});
931
}
932
933
void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt,
934
NoopLattice &L, Environment &Env) {
935
LatticeTransferState State(L, Env);
936
TransferMatchSwitch(Elt, getASTContext(), State);
937
}
938
939
UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(
940
UncheckedOptionalAccessModelOptions Options)
941
: DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
942
943
} // namespace dataflow
944
} // namespace clang
945
946