1//===- UncheckedStatusOrAccessModel.cpp -----------------------------------===//
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#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h"
10
11#include <cassert>
12#include <utility>
13
14#include "clang/AST/DeclCXX.h"
15#include "clang/AST/DeclTemplate.h"
16#include "clang/AST/Expr.h"
17#include "clang/AST/ExprCXX.h"
18#include "clang/AST/TypeBase.h"
19#include "clang/ASTMatchers/ASTMatchFinder.h"
20#include "clang/ASTMatchers/ASTMatchers.h"
21#include "clang/ASTMatchers/ASTMatchersInternal.h"
22#include "clang/Analysis/CFG.h"
23#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
24#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
25#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
26#include "clang/Analysis/FlowSensitive/MatchSwitch.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/LLVM.h"
32#include "clang/Basic/SourceLocation.h"
33#include "llvm/ADT/StringMap.h"
34
35#include "GtestModelHelpers.h"
36
37namespace clang::dataflow::statusor_model {
38namespace {
39
40using ::clang::ast_matchers::MatchFinder;
41using ::clang::ast_matchers::StatementMatcher;
42
43} // namespace
44
45static bool namespaceEquals(const NamespaceDecl *NS,
46 clang::ArrayRef<clang::StringRef> NamespaceNames) {
47 while (!NamespaceNames.empty() && NS) {
48 if (NS->getName() != NamespaceNames.consume_back())
49 return false;
50 NS = dyn_cast_or_null<NamespaceDecl>(Val: NS->getParent());
51 }
52 return NamespaceNames.empty() && !NS;
53}
54
55// TODO: move this to a proper place to share with the rest of clang
56static bool isTypeNamed(QualType Type, clang::ArrayRef<clang::StringRef> NS,
57 StringRef Name) {
58 if (Type.isNull())
59 return false;
60 if (auto *RD = Type->getAsRecordDecl())
61 if (RD->getName() == Name)
62 if (const auto *N = dyn_cast_or_null<NamespaceDecl>(Val: RD->getDeclContext()))
63 return namespaceEquals(NS: N, NamespaceNames: NS);
64 return false;
65}
66
67static bool isStatusOrOperatorBaseType(QualType Type) {
68 return isTypeNamed(Type, NS: {"absl", "internal_statusor"}, Name: "OperatorBase");
69}
70
71static bool isSafeUnwrap(RecordStorageLocation *StatusOrLoc,
72 const Environment &Env) {
73 if (!StatusOrLoc)
74 return false;
75 auto &StatusLoc = locForStatus(StatusOrLoc&: *StatusOrLoc);
76 auto *OkVal = Env.get<BoolValue>(Loc: locForOk(StatusLoc));
77 return OkVal != nullptr && Env.proves(OkVal->formula());
78}
79
80static ClassTemplateSpecializationDecl *
81getStatusOrBaseClass(const QualType &Ty) {
82 auto *RD = Ty->getAsCXXRecordDecl();
83 if (RD == nullptr)
84 return nullptr;
85 if (isStatusOrType(Type: Ty) ||
86 // In case we are analyzing code under OperatorBase itself that uses
87 // operator* (e.g. to implement operator->).
88 isStatusOrOperatorBaseType(Type: Ty))
89 return cast<ClassTemplateSpecializationDecl>(Val: RD);
90 if (!RD->hasDefinition())
91 return nullptr;
92 for (const auto &Base : RD->bases())
93 if (auto *QT = getStatusOrBaseClass(Ty: Base.getType()))
94 return QT;
95 return nullptr;
96}
97
98static QualType getStatusOrValueType(ClassTemplateSpecializationDecl *TRD) {
99 return TRD->getTemplateArgs().get(Idx: 0).getAsType();
100}
101
102static auto ofClassStatus() {
103 using namespace ::clang::ast_matchers;
104 return ofClass(InnerMatcher: hasName(Name: "::absl::Status"));
105}
106
107static auto isStatusMemberCallWithName(llvm::StringRef member_name) {
108 using namespace ::clang::ast_matchers;
109 return cxxMemberCallExpr(
110 on(InnerMatcher: expr(unless(cxxThisExpr()))),
111 callee(InnerMatcher: cxxMethodDecl(hasName(Name: member_name), ofClassStatus())));
112}
113
114static auto isStatusOrMemberCallWithName(llvm::StringRef member_name) {
115 using namespace ::clang::ast_matchers;
116 return cxxMemberCallExpr(
117 on(InnerMatcher: expr(unless(cxxThisExpr()))),
118 callee(InnerMatcher: cxxMethodDecl(
119 hasName(Name: member_name),
120 ofClass(InnerMatcher: anyOf(statusOrClass(), statusOrOperatorBaseClass())))));
121}
122
123static auto isStatusOrOperatorCallWithName(llvm::StringRef operator_name) {
124 using namespace ::clang::ast_matchers;
125 return cxxOperatorCallExpr(
126 hasOverloadedOperatorName(Name: operator_name),
127 callee(InnerMatcher: cxxMethodDecl(
128 ofClass(InnerMatcher: anyOf(statusOrClass(), statusOrOperatorBaseClass())))));
129}
130
131static auto valueCall() {
132 using namespace ::clang::ast_matchers;
133 return anyOf(isStatusOrMemberCallWithName(member_name: "value"),
134 isStatusOrMemberCallWithName(member_name: "ValueOrDie"));
135}
136
137static auto valueOperatorCall() {
138 using namespace ::clang::ast_matchers;
139 return expr(anyOf(isStatusOrOperatorCallWithName(operator_name: "*"),
140 isStatusOrOperatorCallWithName(operator_name: "->")));
141}
142
143static auto isComparisonOperatorCall(llvm::StringRef operator_name) {
144 using namespace ::clang::ast_matchers;
145 return cxxOperatorCallExpr(
146 hasOverloadedOperatorName(Name: operator_name), argumentCountIs(N: 2),
147 hasArgument(N: 0, InnerMatcher: anyOf(hasType(InnerMatcher: statusType()), hasType(InnerMatcher: statusOrType()))),
148 hasArgument(N: 1, InnerMatcher: anyOf(hasType(InnerMatcher: statusType()), hasType(InnerMatcher: statusOrType()))));
149}
150
151static auto isOkStatusCall() {
152 using namespace ::clang::ast_matchers;
153 return callExpr(callee(InnerMatcher: functionDecl(hasName(Name: "::absl::OkStatus"))));
154}
155
156static auto isNotOkStatusCall() {
157 using namespace ::clang::ast_matchers;
158 return callExpr(callee(InnerMatcher: functionDecl(hasAnyName(
159 "::absl::AbortedError", "::absl::AlreadyExistsError",
160 "::absl::CancelledError", "::absl::DataLossError",
161 "::absl::DeadlineExceededError", "::absl::FailedPreconditionError",
162 "::absl::InternalError", "::absl::InvalidArgumentError",
163 "::absl::NotFoundError", "::absl::OutOfRangeError",
164 "::absl::PermissionDeniedError", "::absl::ResourceExhaustedError",
165 "::absl::UnauthenticatedError", "::absl::UnavailableError",
166 "::absl::UnimplementedError", "::absl::UnknownError"))));
167}
168
169static auto isPointerComparisonOperatorCall(std::string operator_name) {
170 using namespace ::clang::ast_matchers;
171 return binaryOperator(hasOperatorName(Name: operator_name),
172 hasLHS(InnerMatcher: hasType(InnerMatcher: hasCanonicalType(InnerMatcher: pointerType(
173 pointee(anyOf(statusOrType(), statusType())))))),
174 hasRHS(InnerMatcher: hasType(InnerMatcher: hasCanonicalType(InnerMatcher: pointerType(
175 pointee(anyOf(statusOrType(), statusType())))))));
176}
177
178// The nullPointerConstant in the two matchers below is to support
179// absl::StatusOr<void*> X = nullptr.
180// nullptr does not match the bound type.
181// TODO: be less restrictive around convertible types in general.
182static auto isStatusOrValueAssignmentCall() {
183 using namespace ::clang::ast_matchers;
184 return cxxOperatorCallExpr(
185 hasOverloadedOperatorName(Name: "="),
186 callee(InnerMatcher: cxxMethodDecl(ofClass(InnerMatcher: statusOrClass()))),
187 hasArgument(N: 1, InnerMatcher: anyOf(hasType(InnerMatcher: hasUnqualifiedDesugaredType(
188 InnerMatcher: type(equalsBoundNode(ID: "T")))),
189 nullPointerConstant())));
190}
191
192static auto isStatusOrValueConstructor() {
193 using namespace ::clang::ast_matchers;
194 return cxxConstructExpr(
195 hasType(InnerMatcher: statusOrType()),
196 hasArgument(N: 0,
197 InnerMatcher: anyOf(hasType(InnerMatcher: hasCanonicalType(InnerMatcher: type(equalsBoundNode(ID: "T")))),
198 nullPointerConstant(),
199 hasType(InnerMatcher: namedDecl(hasAnyName("absl::in_place_t",
200 "std::in_place_t"))))));
201}
202
203static auto isStatusOrConstructor() {
204 using namespace ::clang::ast_matchers;
205 return cxxConstructExpr(hasType(InnerMatcher: statusOrType()));
206}
207
208static auto isStatusConstructor() {
209 using namespace ::clang::ast_matchers;
210 return cxxConstructExpr(hasType(InnerMatcher: statusType()));
211}
212static auto isLoggingGetReferenceableValueCall() {
213 using namespace ::clang::ast_matchers;
214 return callExpr(callee(
215 InnerMatcher: functionDecl(hasName(Name: "::absl::log_internal::GetReferenceableValue"))));
216}
217
218static auto isLoggingCheckEqImpl() {
219 using namespace ::clang::ast_matchers;
220 return callExpr(
221 callee(InnerMatcher: functionDecl(hasName(Name: "::absl::log_internal::Check_EQImpl"))));
222}
223
224static auto isAsStatusCallWithStatus() {
225 using namespace ::clang::ast_matchers;
226 return callExpr(
227 callee(InnerMatcher: functionDecl(hasName(Name: "::absl::log_internal::AsStatus"))),
228 hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: statusClass())));
229}
230
231static auto isAsStatusCallWithStatusOr() {
232 using namespace ::clang::ast_matchers;
233 return callExpr(
234 callee(InnerMatcher: functionDecl(hasName(Name: "::absl::log_internal::AsStatus"))),
235 hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: statusOrType())));
236}
237
238static auto possiblyReferencedStatusOrType() {
239 using namespace ::clang::ast_matchers;
240 return anyOf(statusOrType(), referenceType(pointee(statusOrType())));
241}
242
243static auto isConstAccessorMemberCall() {
244 using namespace ::clang::ast_matchers;
245 return cxxMemberCallExpr(callee(InnerMatcher: cxxMethodDecl(
246 parameterCountIs(N: 0), isConst(),
247 returns(InnerMatcher: hasCanonicalType(InnerMatcher: anyOf(referenceType(), recordType()))))));
248}
249
250static auto isConstAccessorMemberOperatorCall() {
251 using namespace ::clang::ast_matchers;
252 return cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(
253 parameterCountIs(N: 0), isConst(),
254 returns(InnerMatcher: hasCanonicalType(InnerMatcher: anyOf(referenceType(), recordType()))))));
255}
256
257static auto isConstPointerAccessorMemberCall() {
258 using namespace ::clang::ast_matchers;
259 return cxxMemberCallExpr(callee(
260 InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst(), returns(InnerMatcher: pointerType()))));
261}
262
263static auto isConstPointerAccessorMemberOperatorCall() {
264 using namespace ::clang::ast_matchers;
265 return cxxOperatorCallExpr(callee(
266 InnerMatcher: cxxMethodDecl(parameterCountIs(N: 0), isConst(), returns(InnerMatcher: pointerType()))));
267}
268
269static auto isNonConstMemberCall() {
270 using namespace ::clang::ast_matchers;
271 return cxxMemberCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst()))));
272}
273
274static auto isNonConstMemberOperatorCall() {
275 using namespace ::clang::ast_matchers;
276 return cxxOperatorCallExpr(callee(InnerMatcher: cxxMethodDecl(unless(isConst()))));
277}
278
279static auto isMakePredicateFormatterFromIsOkMatcherCall() {
280 using namespace ::clang::ast_matchers;
281 return callExpr(
282 callee(InnerMatcher: functionDecl(
283 hasName(Name: "::testing::internal::MakePredicateFormatterFromMatcher"))),
284 hasArgument(
285 N: 0, InnerMatcher: hasType(InnerMatcher: cxxRecordDecl(hasAnyName(
286 "::testing::status::internal_status::IsOkMatcher",
287 "::absl_testing::status_internal::IsOkMatcher",
288 "::testing::status::internal_status::IsOkAndHoldsMatcher",
289 "::absl_testing::status_internal::IsOkAndHoldsMatcher")))));
290}
291
292static auto isStatusIsOkMatcherCall() {
293 using namespace ::clang::ast_matchers;
294 return callExpr(callee(InnerMatcher: functionDecl(hasAnyName(
295 "::testing::status::StatusIs", "absl_testing::StatusIs",
296 "::testing::status::CanonicalStatusIs",
297 "::absl_testing::CanonicalStatusIs"))),
298 hasArgument(N: 0, InnerMatcher: declRefExpr(to(InnerMatcher: enumConstantDecl(hasAnyName(
299 "::absl::StatusCode::kOk", "OK"))))));
300}
301
302static auto isMakePredicateFormatterFromStatusIsMatcherCall() {
303 using namespace ::clang::ast_matchers;
304 return callExpr(
305 callee(InnerMatcher: functionDecl(
306 hasName(Name: "::testing::internal::MakePredicateFormatterFromMatcher"))),
307 hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: cxxRecordDecl(hasAnyName(
308 "::testing::status::internal_status::StatusIsMatcher",
309 "::testing::status::internal_status::"
310 "CanonicalStatusIsMatcher",
311 "::absl_testing::status_internal::StatusIsMatcher",
312 "::absl_testing::status_internal::"
313 "CanonicalStatusIsMatcher")))));
314}
315
316static auto isPredicateFormatterFromStatusMatcherCall() {
317 using namespace ::clang::ast_matchers;
318 return cxxOperatorCallExpr(
319 hasOverloadedOperatorName(Name: "()"),
320 callee(InnerMatcher: cxxMethodDecl(ofClass(
321 InnerMatcher: hasName(Name: "testing::internal::PredicateFormatterFromMatcher")))),
322 hasArgument(N: 2, InnerMatcher: hasType(InnerMatcher: statusType())));
323}
324
325static auto isPredicateFormatterFromStatusOrMatcherCall() {
326 using namespace ::clang::ast_matchers;
327 return cxxOperatorCallExpr(
328 hasOverloadedOperatorName(Name: "()"),
329 callee(InnerMatcher: cxxMethodDecl(ofClass(
330 InnerMatcher: hasName(Name: "testing::internal::PredicateFormatterFromMatcher")))),
331 hasArgument(N: 2, InnerMatcher: hasType(InnerMatcher: statusOrType())));
332}
333
334static auto isAssertionResultOperatorBoolCall() {
335 using namespace ::clang::ast_matchers;
336 return cxxMemberCallExpr(
337 on(InnerMatcher: expr(unless(cxxThisExpr()))),
338 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "operator bool"),
339 ofClass(InnerMatcher: hasName(Name: "testing::AssertionResult")))));
340}
341
342static auto isAssertionResultConstructFromBoolCall() {
343 using namespace ::clang::ast_matchers;
344 return cxxConstructExpr(
345 hasType(InnerMatcher: recordDecl(hasName(Name: "testing::AssertionResult"))),
346 hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: booleanType())));
347}
348
349static auto isStatusOrReturningCall() {
350 using namespace ::clang::ast_matchers;
351 return callExpr(
352 callee(InnerMatcher: functionDecl(returns(InnerMatcher: possiblyReferencedStatusOrType()))));
353}
354
355static auto isStatusOrPtrReturningCall() {
356 using namespace ::clang::ast_matchers;
357 return callExpr(callee(InnerMatcher: functionDecl(returns(InnerMatcher: hasUnqualifiedDesugaredType(
358 InnerMatcher: pointerType(pointee(possiblyReferencedStatusOrType())))))));
359}
360
361static auto isStatusPtrReturningCall() {
362 using namespace ::clang::ast_matchers;
363 return callExpr(callee(InnerMatcher: functionDecl(returns(InnerMatcher: hasUnqualifiedDesugaredType(
364 InnerMatcher: pointerType(pointee(hasUnqualifiedDesugaredType(
365 InnerMatcher: recordType(hasDeclaration(InnerMatcher: statusClass()))))))))));
366}
367
368static auto ofClassReturnIfErrorAdaptor() {
369 using namespace ::clang::ast_matchers;
370 return ofClass(
371 InnerMatcher: hasName(Name: "::absl::status_macro_internal::ReturnIfErrorAdaptor"));
372}
373
374static auto returnIfErrorAdaptorClass() {
375 using namespace ::clang::ast_matchers;
376 return cxxRecordDecl(
377 hasName(Name: "::absl::status_macro_internal::ReturnIfErrorAdaptor"));
378}
379
380static auto returnIfErrorAdaptorType() {
381 using namespace ::clang::ast_matchers;
382 return hasUnqualifiedDesugaredType(
383 InnerMatcher: recordType(hasDeclaration(InnerMatcher: returnIfErrorAdaptorClass())));
384}
385
386static auto isReturnIfErrorAdaptorOperatorBoolCall() {
387 using namespace ::clang::ast_matchers;
388 return cxxMemberCallExpr(
389 on(InnerMatcher: expr(unless(cxxThisExpr()))),
390 callee(InnerMatcher: cxxMethodDecl(hasName(Name: "operator bool"),
391 ofClassReturnIfErrorAdaptor())));
392}
393
394static auto isMacroAdaptorCall() {
395 using namespace ::clang::ast_matchers;
396 return callExpr(callee(
397 InnerMatcher: functionDecl(hasName(Name: "::absl::status_macro_internal::MacroAdaptor"),
398 returns(InnerMatcher: returnIfErrorAdaptorType()))));
399}
400
401static auto
402buildDiagnoseMatchSwitch(const UncheckedStatusOrAccessModelOptions &Options) {
403 return CFGMatchSwitchBuilder<const Environment,
404 llvm::SmallVector<SourceLocation>>()
405 // StatusOr::value, StatusOr::ValueOrDie
406 .CaseOfCFGStmt<CXXMemberCallExpr>(
407 M: valueCall(),
408 A: [](const CXXMemberCallExpr *E,
409 const ast_matchers::MatchFinder::MatchResult &,
410 const Environment &Env) {
411 if (!isSafeUnwrap(StatusOrLoc: getImplicitObjectLocation(MCE: *E, Env), Env))
412 return llvm::SmallVector<SourceLocation>({E->getExprLoc()});
413 return llvm::SmallVector<SourceLocation>();
414 })
415
416 // StatusOr::operator*, StatusOr::operator->
417 .CaseOfCFGStmt<CXXOperatorCallExpr>(
418 M: valueOperatorCall(),
419 A: [](const CXXOperatorCallExpr *E,
420 const ast_matchers::MatchFinder::MatchResult &,
421 const Environment &Env) {
422 RecordStorageLocation *StatusOrLoc =
423 Env.get<RecordStorageLocation>(E: *E->getArg(Arg: 0));
424 if (!isSafeUnwrap(StatusOrLoc, Env))
425 return llvm::SmallVector<SourceLocation>({E->getOperatorLoc()});
426 return llvm::SmallVector<SourceLocation>();
427 })
428 .Build();
429}
430
431UncheckedStatusOrAccessDiagnoser::UncheckedStatusOrAccessDiagnoser(
432 UncheckedStatusOrAccessModelOptions Options)
433 : DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
434
435llvm::SmallVector<SourceLocation> UncheckedStatusOrAccessDiagnoser::operator()(
436 const CFGElement &Elt, ASTContext &Ctx,
437 const TransferStateForDiagnostics<UncheckedStatusOrAccessModel::Lattice>
438 &State) {
439 return DiagnoseMatchSwitch(Elt, Ctx, State.Env);
440}
441
442BoolValue &initializeStatus(RecordStorageLocation &StatusLoc,
443 Environment &Env) {
444 auto &OkVal = Env.makeAtomicBoolValue();
445 Env.setValue(Loc: locForOk(StatusLoc), Val&: OkVal);
446 return OkVal;
447}
448
449BoolValue &initializeStatusOr(RecordStorageLocation &StatusOrLoc,
450 Environment &Env) {
451 return initializeStatus(StatusLoc&: locForStatus(StatusOrLoc), Env);
452}
453
454clang::ast_matchers::DeclarationMatcher statusOrClass() {
455 using namespace ::clang::ast_matchers;
456 return classTemplateSpecializationDecl(
457 hasName(Name: "absl::StatusOr"),
458 hasTemplateArgument(N: 0, InnerMatcher: refersToType(InnerMatcher: type().bind(ID: "T"))));
459}
460
461clang::ast_matchers::DeclarationMatcher statusClass() {
462 using namespace ::clang::ast_matchers;
463 return cxxRecordDecl(hasName(Name: "absl::Status"));
464}
465
466clang::ast_matchers::DeclarationMatcher statusOrOperatorBaseClass() {
467 using namespace ::clang::ast_matchers;
468 return classTemplateSpecializationDecl(
469 hasName(Name: "absl::internal_statusor::OperatorBase"));
470}
471
472clang::ast_matchers::TypeMatcher statusOrType() {
473 using namespace ::clang::ast_matchers;
474 return hasCanonicalType(InnerMatcher: qualType(hasDeclaration(InnerMatcher: statusOrClass())));
475}
476
477clang::ast_matchers::TypeMatcher statusType() {
478 using namespace ::clang::ast_matchers;
479 return hasCanonicalType(InnerMatcher: qualType(hasDeclaration(InnerMatcher: statusClass())));
480}
481
482bool isStatusOrType(QualType Type) {
483 return isTypeNamed(Type, NS: {"absl"}, Name: "StatusOr");
484}
485
486bool isStatusType(QualType Type) {
487 return isTypeNamed(Type, NS: {"absl"}, Name: "Status");
488}
489
490static bool isPredicateFormatterFromMatcherType(QualType Type) {
491 return isTypeNamed(Type, NS: {"testing", "internal"},
492 Name: "PredicateFormatterFromMatcher");
493}
494
495static bool isAssertionResultType(QualType Type) {
496 return isTypeNamed(Type, NS: {"testing"}, Name: "AssertionResult");
497}
498
499static bool isStatusIsMatcherType(QualType Type) {
500 return isTypeNamed(Type, NS: {"testing", "status", "internal_status"},
501 Name: "StatusIsMatcher") ||
502 isTypeNamed(Type, NS: {"testing", "status", "internal_status"},
503 Name: "CanonicalStatusIsMatcher") ||
504 isTypeNamed(Type, NS: {"absl_testing", "status_internal"},
505 Name: "StatusIsMatcher") ||
506 isTypeNamed(Type, NS: {"absl_testing", "status_internal"},
507 Name: "CanonicalStatusIsMatcher");
508}
509
510static bool IsMacroAdaptorType(clang::QualType type) {
511 return isTypeNamed(Type: type, NS: {"absl", "status_macro_internal"},
512 Name: "ReturnIfErrorAdaptor");
513}
514
515llvm::StringMap<QualType> getSyntheticFields(QualType Ty, QualType StatusType,
516 const CXXRecordDecl &RD) {
517 if (auto *TRD = getStatusOrBaseClass(Ty))
518 return {{"status", StatusType}, {"value", getStatusOrValueType(TRD)}};
519 if (isStatusType(Type: Ty) || (RD.hasDefinition() &&
520 RD.isDerivedFrom(Base: StatusType->getAsCXXRecordDecl())))
521 return {{"ok", RD.getASTContext().BoolTy}};
522 if (isAssertionResultType(Type: Ty))
523 return {{"ok", RD.getASTContext().BoolTy}};
524 if (isPredicateFormatterFromMatcherType(Type: Ty))
525 return {{"ok_predicate", RD.getASTContext().BoolTy}};
526 if (isStatusIsMatcherType(Type: Ty))
527 return {{"ok_matcher", RD.getASTContext().BoolTy}};
528 if (IsMacroAdaptorType(type: Ty))
529 return {{"status", StatusType}};
530 return {};
531}
532
533RecordStorageLocation &locForStatus(RecordStorageLocation &StatusOrLoc) {
534 return cast<RecordStorageLocation>(Val&: StatusOrLoc.getSyntheticField(Name: "status"));
535}
536
537StorageLocation &locForOk(RecordStorageLocation &StatusLoc) {
538 return StatusLoc.getSyntheticField(Name: "ok");
539}
540
541BoolValue &valForOk(RecordStorageLocation &StatusLoc, Environment &Env) {
542 if (auto *Val = Env.get<BoolValue>(Loc: locForOk(StatusLoc)))
543 return *Val;
544 return initializeStatus(StatusLoc, Env);
545}
546static StorageLocation &locForOkPredicate(RecordStorageLocation &StatusLoc) {
547 return StatusLoc.getSyntheticField(Name: "ok_predicate");
548}
549
550static StorageLocation &locForOkMatcher(RecordStorageLocation &StatusLoc) {
551 return StatusLoc.getSyntheticField(Name: "ok_matcher");
552}
553
554static void transferStatusOrOkCall(const CXXMemberCallExpr *Expr,
555 const MatchFinder::MatchResult &,
556 LatticeTransferState &State) {
557 RecordStorageLocation *StatusOrLoc =
558 getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
559 if (StatusOrLoc == nullptr)
560 return;
561
562 auto &OkVal = valForOk(StatusLoc&: locForStatus(StatusOrLoc&: *StatusOrLoc), Env&: State.Env);
563 State.Env.setValue(E: *Expr, Val&: OkVal);
564}
565
566static void transferStatusCall(const CXXMemberCallExpr *Expr,
567 const MatchFinder::MatchResult &,
568 LatticeTransferState &State) {
569 RecordStorageLocation *StatusOrLoc =
570 getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
571 if (StatusOrLoc == nullptr)
572 return;
573
574 RecordStorageLocation &StatusLoc = locForStatus(StatusOrLoc&: *StatusOrLoc);
575
576 if (State.Env.getValue(Loc: locForOk(StatusLoc)) == nullptr)
577 initializeStatusOr(StatusOrLoc&: *StatusOrLoc, Env&: State.Env);
578
579 if (Expr->isPRValue())
580 copyRecord(Src&: StatusLoc, Dst&: State.Env.getResultObjectLocation(RecordPRValue: *Expr), Env&: State.Env);
581 else
582 State.Env.setStorageLocation(E: *Expr, Loc&: StatusLoc);
583}
584
585static void transferStatusOkCall(const CXXMemberCallExpr *Expr,
586 const MatchFinder::MatchResult &,
587 LatticeTransferState &State) {
588 RecordStorageLocation *StatusLoc =
589 getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
590 if (StatusLoc == nullptr)
591 return;
592
593 if (Value *Val = State.Env.getValue(Loc: locForOk(StatusLoc&: *StatusLoc)))
594 State.Env.setValue(E: *Expr, Val&: *Val);
595}
596
597static void transferStatusUpdateCall(const CXXMemberCallExpr *Expr,
598 const MatchFinder::MatchResult &,
599 LatticeTransferState &State) {
600 // S.Update(OtherS) sets S to the error code of OtherS if it is OK,
601 // otherwise does nothing.
602 assert(Expr->getNumArgs() == 1);
603 auto *Arg = Expr->getArg(Arg: 0);
604 RecordStorageLocation *ArgRecord =
605 Arg->isPRValue() ? &State.Env.getResultObjectLocation(RecordPRValue: *Arg)
606 : State.Env.get<RecordStorageLocation>(E: *Arg);
607 RecordStorageLocation *ThisLoc = getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
608 if (ThisLoc == nullptr || ArgRecord == nullptr)
609 return;
610
611 auto &ThisOkVal = valForOk(StatusLoc&: *ThisLoc, Env&: State.Env);
612 auto &ArgOkVal = valForOk(StatusLoc&: *ArgRecord, Env&: State.Env);
613 auto &A = State.Env.arena();
614 auto &NewVal = State.Env.makeAtomicBoolValue();
615 State.Env.assume(A.makeImplies(LHS: A.makeNot(Val: ThisOkVal.formula()),
616 RHS: A.makeNot(Val: NewVal.formula())));
617 State.Env.assume(A.makeImplies(LHS: NewVal.formula(), RHS: ArgOkVal.formula()));
618 State.Env.setValue(Loc: locForOk(StatusLoc&: *ThisLoc), Val&: NewVal);
619}
620
621static BoolValue *evaluateStatusEquality(RecordStorageLocation &LhsStatusLoc,
622 RecordStorageLocation &RhsStatusLoc,
623 Environment &Env) {
624 auto &A = Env.arena();
625 // Logically, a Status object is composed of an error code that could take one
626 // of multiple possible values, including the "ok" value. We track whether a
627 // Status object has an "ok" value and represent this as an `ok` bit. Equality
628 // of Status objects compares their error codes. Therefore, merely comparing
629 // the `ok` bits isn't sufficient: when two Status objects are assigned non-ok
630 // error codes the equality of their respective error codes matters. Since we
631 // only track the `ok` bits, we can't make any conclusions about equality when
632 // we know that two Status objects have non-ok values.
633
634 auto &LhsOkVal = valForOk(StatusLoc&: LhsStatusLoc, Env);
635 auto &RhsOkVal = valForOk(StatusLoc&: RhsStatusLoc, Env);
636
637 auto &Res = Env.makeAtomicBoolValue();
638
639 // lhs && rhs => res (a.k.a. !res => !lhs || !rhs)
640 Env.assume(A.makeImplies(LHS: A.makeAnd(LHS: LhsOkVal.formula(), RHS: RhsOkVal.formula()),
641 RHS: Res.formula()));
642 // res => (lhs == rhs)
643 Env.assume(A.makeImplies(
644 LHS: Res.formula(), RHS: A.makeEquals(LHS: LhsOkVal.formula(), RHS: RhsOkVal.formula())));
645
646 return &Res;
647}
648
649static BoolValue *
650evaluateStatusOrEquality(RecordStorageLocation &LhsStatusOrLoc,
651 RecordStorageLocation &RhsStatusOrLoc,
652 Environment &Env) {
653 auto &A = Env.arena();
654 // Logically, a StatusOr<T> object is composed of two values - a Status and a
655 // value of type T. Equality of StatusOr objects compares both values.
656 // Therefore, merely comparing the `ok` bits of the Status values isn't
657 // sufficient. When two StatusOr objects are engaged, the equality of their
658 // respective values of type T matters. Similarly, when two StatusOr objects
659 // have Status values that have non-ok error codes, the equality of the error
660 // codes matters. Since we only track the `ok` bits of the Status values, we
661 // can't make any conclusions about equality when we know that two StatusOr
662 // objects are engaged or when their Status values contain non-ok error codes.
663 auto &LhsOkVal = valForOk(StatusLoc&: locForStatus(StatusOrLoc&: LhsStatusOrLoc), Env);
664 auto &RhsOkVal = valForOk(StatusLoc&: locForStatus(StatusOrLoc&: RhsStatusOrLoc), Env);
665 auto &res = Env.makeAtomicBoolValue();
666
667 // res => (lhs == rhs)
668 Env.assume(A.makeImplies(
669 LHS: res.formula(), RHS: A.makeEquals(LHS: LhsOkVal.formula(), RHS: RhsOkVal.formula())));
670 return &res;
671}
672
673static BoolValue *evaluateEquality(const Expr *LhsExpr, const Expr *RhsExpr,
674 Environment &Env) {
675 // Check the type of both sides in case an operator== is added that admits
676 // different types.
677 if (isStatusOrType(Type: LhsExpr->getType()) &&
678 isStatusOrType(Type: RhsExpr->getType())) {
679 auto *LhsStatusOrLoc = Env.get<RecordStorageLocation>(E: *LhsExpr);
680 if (LhsStatusOrLoc == nullptr)
681 return nullptr;
682 auto *RhsStatusOrLoc = Env.get<RecordStorageLocation>(E: *RhsExpr);
683 if (RhsStatusOrLoc == nullptr)
684 return nullptr;
685
686 return evaluateStatusOrEquality(LhsStatusOrLoc&: *LhsStatusOrLoc, RhsStatusOrLoc&: *RhsStatusOrLoc, Env);
687 }
688 if (isStatusType(Type: LhsExpr->getType()) && isStatusType(Type: RhsExpr->getType())) {
689 auto *LhsStatusLoc = Env.get<RecordStorageLocation>(E: *LhsExpr);
690 if (LhsStatusLoc == nullptr)
691 return nullptr;
692
693 auto *RhsStatusLoc = Env.get<RecordStorageLocation>(E: *RhsExpr);
694 if (RhsStatusLoc == nullptr)
695 return nullptr;
696
697 return evaluateStatusEquality(LhsStatusLoc&: *LhsStatusLoc, RhsStatusLoc&: *RhsStatusLoc, Env);
698 }
699 return nullptr;
700}
701
702static void transferComparisonOperator(const CXXOperatorCallExpr *Expr,
703 LatticeTransferState &State,
704 bool IsNegative) {
705 auto *LhsAndRhsVal =
706 evaluateEquality(LhsExpr: Expr->getArg(Arg: 0), RhsExpr: Expr->getArg(Arg: 1), Env&: State.Env);
707 if (LhsAndRhsVal == nullptr)
708 return;
709
710 if (IsNegative)
711 State.Env.setValue(E: *Expr, Val&: State.Env.makeNot(Val&: *LhsAndRhsVal));
712 else
713 State.Env.setValue(E: *Expr, Val&: *LhsAndRhsVal);
714}
715
716static RecordStorageLocation *getPointeeLocation(const Expr &Expr,
717 Environment &Env) {
718 if (auto *PointerVal = Env.get<PointerValue>(E: Expr))
719 return dyn_cast<RecordStorageLocation>(Val: &PointerVal->getPointeeLoc());
720 return nullptr;
721}
722
723static BoolValue *evaluatePointerEquality(const Expr *LhsExpr,
724 const Expr *RhsExpr,
725 Environment &Env) {
726 assert(LhsExpr->getType()->isPointerType());
727 assert(RhsExpr->getType()->isPointerType());
728 RecordStorageLocation *LhsStatusLoc = nullptr;
729 RecordStorageLocation *RhsStatusLoc = nullptr;
730 if (isStatusOrType(Type: LhsExpr->getType()->getPointeeType()) &&
731 isStatusOrType(Type: RhsExpr->getType()->getPointeeType())) {
732 auto *LhsStatusOrLoc = getPointeeLocation(Expr: *LhsExpr, Env);
733 auto *RhsStatusOrLoc = getPointeeLocation(Expr: *RhsExpr, Env);
734 if (LhsStatusOrLoc == nullptr || RhsStatusOrLoc == nullptr)
735 return nullptr;
736 LhsStatusLoc = &locForStatus(StatusOrLoc&: *LhsStatusOrLoc);
737 RhsStatusLoc = &locForStatus(StatusOrLoc&: *RhsStatusOrLoc);
738 } else if (isStatusType(Type: LhsExpr->getType()->getPointeeType()) &&
739 isStatusType(Type: RhsExpr->getType()->getPointeeType())) {
740 LhsStatusLoc = getPointeeLocation(Expr: *LhsExpr, Env);
741 RhsStatusLoc = getPointeeLocation(Expr: *RhsExpr, Env);
742 }
743 if (LhsStatusLoc == nullptr || RhsStatusLoc == nullptr)
744 return nullptr;
745 auto &LhsOkVal = valForOk(StatusLoc&: *LhsStatusLoc, Env);
746 auto &RhsOkVal = valForOk(StatusLoc&: *RhsStatusLoc, Env);
747 auto &Res = Env.makeAtomicBoolValue();
748 auto &A = Env.arena();
749 Env.assume(A.makeImplies(
750 LHS: Res.formula(), RHS: A.makeEquals(LHS: LhsOkVal.formula(), RHS: RhsOkVal.formula())));
751 return &Res;
752}
753
754static void transferPointerComparisonOperator(const BinaryOperator *Expr,
755 LatticeTransferState &State,
756 bool IsNegative) {
757 auto *LhsAndRhsVal =
758 evaluatePointerEquality(LhsExpr: Expr->getLHS(), RhsExpr: Expr->getRHS(), Env&: State.Env);
759 if (LhsAndRhsVal == nullptr)
760 return;
761
762 if (IsNegative)
763 State.Env.setValue(E: *Expr, Val&: State.Env.makeNot(Val&: *LhsAndRhsVal));
764 else
765 State.Env.setValue(E: *Expr, Val&: *LhsAndRhsVal);
766}
767
768static void transferOkStatusCall(const CallExpr *Expr,
769 const MatchFinder::MatchResult &,
770 LatticeTransferState &State) {
771 auto &OkVal =
772 initializeStatus(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr), Env&: State.Env);
773 State.Env.assume(OkVal.formula());
774}
775
776static void transferNotOkStatusCall(const CallExpr *Expr,
777 const MatchFinder::MatchResult &,
778 LatticeTransferState &State) {
779 auto &OkVal =
780 initializeStatus(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr), Env&: State.Env);
781 auto &A = State.Env.arena();
782 State.Env.assume(A.makeNot(Val: OkVal.formula()));
783}
784
785static void transferEmplaceCall(const CXXMemberCallExpr *Expr,
786 const MatchFinder::MatchResult &,
787 LatticeTransferState &State) {
788 RecordStorageLocation *StatusOrLoc =
789 getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
790 if (StatusOrLoc == nullptr)
791 return;
792
793 auto &OkVal = valForOk(StatusLoc&: locForStatus(StatusOrLoc&: *StatusOrLoc), Env&: State.Env);
794 State.Env.assume(OkVal.formula());
795}
796
797static void transferValueAssignmentCall(const CXXOperatorCallExpr *Expr,
798 const MatchFinder::MatchResult &,
799 LatticeTransferState &State) {
800 assert(Expr->getNumArgs() > 1);
801
802 auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
803 if (StatusOrLoc == nullptr)
804 return;
805
806 auto &OkVal = initializeStatusOr(StatusOrLoc&: *StatusOrLoc, Env&: State.Env);
807 State.Env.assume(OkVal.formula());
808}
809
810static void transferValueConstructor(const CXXConstructExpr *Expr,
811 const MatchFinder::MatchResult &,
812 LatticeTransferState &State) {
813 auto &OkVal =
814 initializeStatusOr(StatusOrLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr), Env&: State.Env);
815 State.Env.assume(OkVal.formula());
816}
817
818static void transferStatusOrConstructor(const CXXConstructExpr *Expr,
819 const MatchFinder::MatchResult &,
820 LatticeTransferState &State) {
821 RecordStorageLocation &StatusOrLoc = State.Env.getResultObjectLocation(RecordPRValue: *Expr);
822 RecordStorageLocation &StatusLoc = locForStatus(StatusOrLoc);
823
824 if (State.Env.getValue(Loc: locForOk(StatusLoc)) == nullptr)
825 initializeStatusOr(StatusOrLoc, Env&: State.Env);
826}
827
828static void transferStatusConstructor(const CXXConstructExpr *Expr,
829 const MatchFinder::MatchResult &,
830 LatticeTransferState &State) {
831 RecordStorageLocation &StatusLoc = State.Env.getResultObjectLocation(RecordPRValue: *Expr);
832
833 if (State.Env.getValue(Loc: locForOk(StatusLoc)) == nullptr)
834 initializeStatus(StatusLoc, Env&: State.Env);
835}
836static void
837transferLoggingGetReferenceableValueCall(const CallExpr *Expr,
838 const MatchFinder::MatchResult &,
839 LatticeTransferState &State) {
840 assert(Expr->getNumArgs() == 1);
841 if (Expr->getArg(Arg: 0)->isPRValue())
842 return;
843 auto *ArgLoc = State.Env.getStorageLocation(E: *Expr->getArg(Arg: 0));
844 if (ArgLoc == nullptr)
845 return;
846
847 State.Env.setStorageLocation(E: *Expr, Loc&: *ArgLoc);
848}
849
850static void transferLoggingCheckEqImpl(const CallExpr *Expr,
851 const MatchFinder::MatchResult &,
852 LatticeTransferState &State) {
853 assert(Expr->getNumArgs() > 2);
854
855 auto *EqVal = evaluateEquality(LhsExpr: Expr->getArg(Arg: 0), RhsExpr: Expr->getArg(Arg: 1), Env&: State.Env);
856 if (EqVal == nullptr)
857 return;
858
859 // Consider modelling this more accurately instead of assigning BoolValue
860 // as the value of an expression of pointer type.
861 // For now, this is being handled in transferPointerToBoolean.
862 State.Env.setValue(E: *Expr, Val&: State.Env.makeNot(Val&: *EqVal));
863}
864
865static void transferAsStatusCallWithStatus(const CallExpr *Expr,
866 const MatchFinder::MatchResult &,
867 LatticeTransferState &State) {
868 assert(Expr->getNumArgs() == 1);
869
870 auto *ArgLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
871 if (ArgLoc == nullptr)
872 return;
873
874 if (State.Env.getValue(Loc: locForOk(StatusLoc&: *ArgLoc)) == nullptr)
875 initializeStatus(StatusLoc&: *ArgLoc, Env&: State.Env);
876
877 auto &ExprVal = State.Env.create<PointerValue>(args&: *ArgLoc);
878 State.Env.setValue(E: *Expr, Val&: ExprVal);
879}
880
881static void transferAsStatusCallWithStatusOr(const CallExpr *Expr,
882 const MatchFinder::MatchResult &,
883 LatticeTransferState &State) {
884 assert(Expr->getNumArgs() == 1);
885
886 auto *ArgLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
887 if (ArgLoc == nullptr)
888 return;
889
890 RecordStorageLocation &StatusLoc = locForStatus(StatusOrLoc&: *ArgLoc);
891
892 if (State.Env.getValue(Loc: locForOk(StatusLoc)) == nullptr)
893 initializeStatusOr(StatusOrLoc&: *ArgLoc, Env&: State.Env);
894
895 auto &ExprVal = State.Env.create<PointerValue>(args&: StatusLoc);
896 State.Env.setValue(E: *Expr, Val&: ExprVal);
897}
898
899static void transferPointerToBoolean(const ImplicitCastExpr *Expr,
900 const MatchFinder::MatchResult &,
901 LatticeTransferState &State) {
902 if (auto *SubExprVal =
903 dyn_cast_or_null<BoolValue>(Val: State.Env.getValue(E: *Expr->getSubExpr())))
904 State.Env.setValue(E: *Expr, Val&: *SubExprVal);
905}
906
907static void transferStatusOrReturningCall(const CallExpr *Expr,
908 LatticeTransferState &State) {
909 RecordStorageLocation *StatusOrLoc =
910 Expr->isPRValue() ? &State.Env.getResultObjectLocation(RecordPRValue: *Expr)
911 : State.Env.get<RecordStorageLocation>(E: *Expr);
912 if (StatusOrLoc != nullptr &&
913 State.Env.getValue(Loc: locForOk(StatusLoc&: locForStatus(StatusOrLoc&: *StatusOrLoc))) == nullptr)
914 initializeStatusOr(StatusOrLoc&: *StatusOrLoc, Env&: State.Env);
915}
916
917static bool doHandleConstAccessorMemberCall(
918 const CallExpr *Expr, RecordStorageLocation *RecordLoc,
919 const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
920 if (RecordLoc == nullptr)
921 return false;
922 const FunctionDecl *DirectCallee = Expr->getDirectCallee();
923 if (DirectCallee == nullptr)
924 return false;
925 StorageLocation &Loc =
926 State.Lattice.getOrCreateConstMethodReturnStorageLocation(
927 RecordLoc: *RecordLoc, Callee: DirectCallee, Env&: State.Env, Initialize: [&](StorageLocation &Loc) {
928 if (isStatusOrType(Type: Expr->getType()))
929 initializeStatusOr(StatusOrLoc&: cast<RecordStorageLocation>(Val&: Loc), Env&: State.Env);
930 });
931 if (Expr->isPRValue()) {
932 auto &ResultLoc = State.Env.getResultObjectLocation(RecordPRValue: *Expr);
933 copyRecord(Src&: cast<RecordStorageLocation>(Val&: Loc), Dst&: ResultLoc, Env&: State.Env);
934 } else {
935 State.Env.setStorageLocation(E: *Expr, Loc);
936 }
937 return true;
938}
939
940static void handleConstAccessorMemberCall(
941 const CallExpr *Expr, RecordStorageLocation *RecordLoc,
942 const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
943 if (!doHandleConstAccessorMemberCall(Expr, RecordLoc, Result, State) &&
944 isStatusOrType(Type: Expr->getType()))
945 transferStatusOrReturningCall(Expr, State);
946}
947static void handleConstPointerAccessorMemberCall(
948 const CallExpr *Expr, RecordStorageLocation *RecordLoc,
949 const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
950 if (RecordLoc == nullptr)
951 return;
952 auto *Val = State.Lattice.getOrCreateConstMethodReturnValue(RecordLoc: *RecordLoc, CE: Expr,
953 Env&: State.Env);
954 State.Env.setValue(E: *Expr, Val&: *Val);
955}
956
957static void
958transferConstAccessorMemberCall(const CXXMemberCallExpr *Expr,
959 const MatchFinder::MatchResult &Result,
960 LatticeTransferState &State) {
961 auto Type = Expr->getType();
962 if (!Type->isRecordType() && !Type->isReferenceType())
963 return;
964 handleConstAccessorMemberCall(
965 Expr, RecordLoc: getImplicitObjectLocation(MCE: *Expr, Env: State.Env), Result, State);
966}
967
968static void
969transferConstAccessorMemberOperatorCall(const CXXOperatorCallExpr *Expr,
970 const MatchFinder::MatchResult &Result,
971 LatticeTransferState &State) {
972 auto Type = Expr->getArg(Arg: 0)->getType();
973 if (!Type->isRecordType() && !Type->isReferenceType())
974 return;
975 auto *RecordLoc = cast_or_null<RecordStorageLocation>(
976 Val: State.Env.getStorageLocation(E: *Expr->getArg(Arg: 0)));
977 handleConstAccessorMemberCall(Expr, RecordLoc, Result, State);
978}
979
980static void
981transferConstPointerAccessorMemberCall(const CXXMemberCallExpr *Expr,
982 const MatchFinder::MatchResult &Result,
983 LatticeTransferState &State) {
984 handleConstPointerAccessorMemberCall(
985 Expr, RecordLoc: getImplicitObjectLocation(MCE: *Expr, Env: State.Env), Result, State);
986}
987
988static void transferConstPointerAccessorMemberOperatorCall(
989 const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &Result,
990 LatticeTransferState &State) {
991 auto *RecordLoc = cast_or_null<RecordStorageLocation>(
992 Val: State.Env.getStorageLocation(E: *Expr->getArg(Arg: 0)));
993 handleConstPointerAccessorMemberCall(Expr, RecordLoc, Result, State);
994}
995
996static void handleNonConstMemberCall(const CallExpr *Expr,
997 RecordStorageLocation *RecordLoc,
998 const MatchFinder::MatchResult &Result,
999 LatticeTransferState &State) {
1000 if (RecordLoc) {
1001 State.Lattice.clearConstMethodReturnValues(RecordLoc: *RecordLoc);
1002 State.Lattice.clearConstMethodReturnStorageLocations(RecordLoc: *RecordLoc);
1003 }
1004 if (isStatusOrType(Type: Expr->getType()))
1005 transferStatusOrReturningCall(Expr, State);
1006}
1007
1008static void transferNonConstMemberCall(const CXXMemberCallExpr *Expr,
1009 const MatchFinder::MatchResult &Result,
1010 LatticeTransferState &State) {
1011 handleNonConstMemberCall(Expr, RecordLoc: getImplicitObjectLocation(MCE: *Expr, Env: State.Env),
1012 Result, State);
1013}
1014
1015static void
1016transferNonConstMemberOperatorCall(const CXXOperatorCallExpr *Expr,
1017 const MatchFinder::MatchResult &Result,
1018 LatticeTransferState &State) {
1019 auto *RecordLoc = cast_or_null<RecordStorageLocation>(
1020 Val: State.Env.getStorageLocation(E: *Expr->getArg(Arg: 0)));
1021 handleNonConstMemberCall(Expr, RecordLoc, Result, State);
1022}
1023
1024static void transferMakePredicateFormatterFromIsOkMatcherCall(
1025 const CallExpr *Expr, const MatchFinder::MatchResult &,
1026 LatticeTransferState &State) {
1027 State.Env.setValue(
1028 Loc: locForOkPredicate(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)),
1029 Val&: State.Env.getBoolLiteralValue(Value: true));
1030}
1031
1032static void transferStatusIsOkMatcherCall(const CallExpr *Expr,
1033 const MatchFinder::MatchResult &,
1034 LatticeTransferState &State) {
1035 BoolValue &OkMatcherVal = State.Env.getBoolLiteralValue(Value: true);
1036 State.Env.setValue(Loc: locForOkMatcher(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)),
1037 Val&: OkMatcherVal);
1038}
1039
1040static void transferMakePredicateFormatterFromStatusIsMatcherCall(
1041 const CallExpr *Expr, const MatchFinder::MatchResult &,
1042 LatticeTransferState &State) {
1043 assert(Expr->isPRValue());
1044 auto &Loc = State.Env.getResultObjectLocation(RecordPRValue: *Expr->getArg(Arg: 0));
1045 auto &OkMatcherLoc = locForOkMatcher(StatusLoc&: Loc);
1046 BoolValue *OkMatcherVal = State.Env.get<BoolValue>(Loc: OkMatcherLoc);
1047 if (OkMatcherVal == nullptr)
1048 return;
1049 State.Env.setValue(
1050 Loc: locForOkPredicate(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)),
1051 Val&: *OkMatcherVal);
1052}
1053
1054static void
1055transferPredicateFormatterMatcherCall(const CXXOperatorCallExpr *Expr,
1056 LatticeTransferState &State,
1057 bool IsStatusOr) {
1058 auto *Loc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
1059 if (Loc == nullptr)
1060 return;
1061
1062 auto *ObjectLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 2));
1063 if (ObjectLoc == nullptr)
1064 return;
1065
1066 auto &OkPredicateLoc = locForOkPredicate(StatusLoc&: *Loc);
1067 BoolValue *OkPredicateVal = State.Env.get<BoolValue>(Loc: OkPredicateLoc);
1068 if (OkPredicateVal == nullptr)
1069 return;
1070
1071 if (IsStatusOr)
1072 ObjectLoc = &locForStatus(StatusOrLoc&: *ObjectLoc);
1073 auto &StatusOk = valForOk(StatusLoc&: *ObjectLoc, Env&: State.Env);
1074
1075 auto &A = State.Env.arena();
1076 auto &Res = State.Env.makeAtomicBoolValue();
1077 State.Env.assume(
1078 A.makeImplies(LHS: OkPredicateVal->formula(),
1079 RHS: A.makeEquals(LHS: StatusOk.formula(), RHS: Res.formula())));
1080 State.Env.setValue(Loc: locForOk(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)), Val&: Res);
1081}
1082
1083static void
1084transferAssertionResultConstructFromBoolCall(const CXXConstructExpr *Expr,
1085 const MatchFinder::MatchResult &,
1086 LatticeTransferState &State) {
1087 assert(Expr->getNumArgs() > 0);
1088
1089 auto *StatusAdaptorLoc = State.Env.get<StorageLocation>(E: *Expr->getArg(Arg: 0));
1090 if (StatusAdaptorLoc == nullptr)
1091 return;
1092 BoolValue *OkVal = State.Env.get<BoolValue>(Loc: *StatusAdaptorLoc);
1093 if (OkVal == nullptr)
1094 return;
1095 State.Env.setValue(Loc: locForOk(StatusLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)),
1096 Val&: *OkVal);
1097}
1098
1099static void
1100transferAssertionResultOperatorBoolCall(const CXXMemberCallExpr *Expr,
1101 const MatchFinder::MatchResult &,
1102 LatticeTransferState &State) {
1103 auto *RecordLoc = getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
1104 if (RecordLoc == nullptr)
1105 return;
1106 BoolValue *OkVal = State.Env.get<BoolValue>(Loc: locForOk(StatusLoc&: *RecordLoc));
1107 if (OkVal == nullptr)
1108 return;
1109 auto &A = State.Env.arena();
1110 auto &Res = State.Env.makeAtomicBoolValue();
1111 State.Env.assume(A.makeEquals(LHS: OkVal->formula(), RHS: Res.formula()));
1112 State.Env.setValue(E: *Expr, Val&: Res);
1113}
1114
1115static void transferDerefCall(const CXXOperatorCallExpr *Expr,
1116 const MatchFinder::MatchResult &,
1117 LatticeTransferState &State) {
1118 auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
1119
1120 if (StatusOrLoc && State.Env.getStorageLocation(E: *Expr) == nullptr)
1121 State.Env.setStorageLocation(E: *Expr,
1122 Loc&: StatusOrLoc->getSyntheticField(Name: "value"));
1123}
1124
1125static void transferArrowCall(const CXXOperatorCallExpr *Expr,
1126 const MatchFinder::MatchResult &,
1127 LatticeTransferState &State) {
1128 auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
1129 if (!StatusOrLoc)
1130 return;
1131 State.Env.setValue(E: *Expr, Val&: State.Env.create<PointerValue>(
1132 args&: StatusOrLoc->getSyntheticField(Name: "value")));
1133}
1134
1135static void transferValueCall(const CXXMemberCallExpr *Expr,
1136 const MatchFinder::MatchResult &,
1137 LatticeTransferState &State) {
1138 auto *StatusOrLoc = getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
1139
1140 if (StatusOrLoc && State.Env.getStorageLocation(E: *Expr) == nullptr)
1141 State.Env.setStorageLocation(E: *Expr,
1142 Loc&: StatusOrLoc->getSyntheticField(Name: "value"));
1143}
1144
1145static void transferStatusOrPtrReturningCall(const CallExpr *Expr,
1146 const MatchFinder::MatchResult &,
1147 LatticeTransferState &State) {
1148 PointerValue *PointerVal =
1149 dyn_cast_or_null<PointerValue>(Val: State.Env.getValue(E: *Expr));
1150 if (!PointerVal) {
1151 PointerVal = cast<PointerValue>(Val: State.Env.createValue(Type: Expr->getType()));
1152 State.Env.setValue(E: *Expr, Val&: *PointerVal);
1153 }
1154
1155 auto *RecordLoc =
1156 dyn_cast_or_null<RecordStorageLocation>(Val: &PointerVal->getPointeeLoc());
1157 if (RecordLoc != nullptr &&
1158 State.Env.getValue(Loc: locForOk(StatusLoc&: locForStatus(StatusOrLoc&: *RecordLoc))) == nullptr)
1159 initializeStatusOr(StatusOrLoc&: *RecordLoc, Env&: State.Env);
1160}
1161
1162static void transferStatusPtrReturningCall(const CallExpr *Expr,
1163 const MatchFinder::MatchResult &,
1164 LatticeTransferState &State) {
1165 PointerValue *PointerVal =
1166 dyn_cast_or_null<PointerValue>(Val: State.Env.getValue(E: *Expr));
1167 if (!PointerVal) {
1168 PointerVal = cast<PointerValue>(Val: State.Env.createValue(Type: Expr->getType()));
1169 State.Env.setValue(E: *Expr, Val&: *PointerVal);
1170 }
1171
1172 auto *RecordLoc =
1173 dyn_cast_or_null<RecordStorageLocation>(Val: &PointerVal->getPointeeLoc());
1174 if (RecordLoc != nullptr &&
1175 State.Env.getValue(Loc: locForOk(StatusLoc&: *RecordLoc)) == nullptr)
1176 initializeStatus(StatusLoc&: *RecordLoc, Env&: State.Env);
1177}
1178
1179static RecordStorageLocation *
1180getSmartPtrLikeStorageLocation(const Expr &E, const Environment &Env) {
1181 if (!E.isPRValue())
1182 return dyn_cast_or_null<RecordStorageLocation>(Val: Env.getStorageLocation(E));
1183 if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Val: Env.getValue(E)))
1184 return dyn_cast_or_null<RecordStorageLocation>(
1185 Val: &PointerVal->getPointeeLoc());
1186 return nullptr;
1187}
1188static void transferMacroAdaptorCall(const clang::CallExpr *Expr,
1189 const MatchFinder::MatchResult &,
1190 LatticeTransferState &State) {
1191 assert(Expr->getNumArgs() > 0);
1192
1193 auto *StatusAdaptorLoc =
1194 State.Env.get<RecordStorageLocation>(E: *Expr->getArg(Arg: 0));
1195 if (StatusAdaptorLoc == nullptr)
1196 return;
1197
1198 copyRecord(Src&: *StatusAdaptorLoc,
1199 Dst&: locForStatus(StatusOrLoc&: State.Env.getResultObjectLocation(RecordPRValue: *Expr)), Env&: State.Env);
1200}
1201
1202static void transferReturnIfErrorAdaptorOperatorBoolCall(
1203 const clang::CXXMemberCallExpr *Expr, const MatchFinder::MatchResult &,
1204 LatticeTransferState &State) {
1205 RecordStorageLocation *StatusAdaptorLoc =
1206 getImplicitObjectLocation(MCE: *Expr, Env: State.Env);
1207 if (StatusAdaptorLoc == nullptr)
1208 return;
1209
1210 auto &OkVal = valForOk(StatusLoc&: locForStatus(StatusOrLoc&: *StatusAdaptorLoc), Env&: State.Env);
1211 State.Env.setValue(E: *Expr, Val&: OkVal);
1212}
1213
1214CFGMatchSwitch<LatticeTransferState>
1215buildTransferMatchSwitch(ASTContext &Ctx,
1216 CFGMatchSwitchBuilder<LatticeTransferState> Builder) {
1217 using namespace ::clang::ast_matchers;
1218 return std::move(Builder)
1219 .CaseOfCFGStmt<CallExpr>(
1220 M: isMakePredicateFormatterFromIsOkMatcherCall(),
1221 A: transferMakePredicateFormatterFromIsOkMatcherCall)
1222 .CaseOfCFGStmt<CallExpr>(M: isStatusIsOkMatcherCall(),
1223 A: transferStatusIsOkMatcherCall)
1224 .CaseOfCFGStmt<CallExpr>(
1225 M: isMakePredicateFormatterFromStatusIsMatcherCall(),
1226 A: transferMakePredicateFormatterFromStatusIsMatcherCall)
1227 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1228 M: isPredicateFormatterFromStatusOrMatcherCall(),
1229 A: [](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
1230 LatticeTransferState &State) {
1231 transferPredicateFormatterMatcherCall(Expr, State,
1232 /*IsStatusOr=*/true);
1233 })
1234 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1235 M: isPredicateFormatterFromStatusMatcherCall(),
1236 A: [](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
1237 LatticeTransferState &State) {
1238 transferPredicateFormatterMatcherCall(Expr, State,
1239 /*IsStatusOr=*/false);
1240 })
1241 .CaseOfCFGStmt<CXXConstructExpr>(
1242 M: isAssertionResultConstructFromBoolCall(),
1243 A: transferAssertionResultConstructFromBoolCall)
1244 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isAssertionResultOperatorBoolCall(),
1245 A: transferAssertionResultOperatorBoolCall)
1246 .CaseOfCFGStmt<CXXMemberCallExpr>(
1247 M: gtest::isAssertionResultExpectationOperatorBoolCall(),
1248 A: [](const CXXMemberCallExpr *Expr, const MatchFinder::MatchResult &,
1249 LatticeTransferState &State) {
1250 return gtest::transferAssertionResultExpectationOperatorBoolCall(
1251 Expr, Env&: State.Env, GetOk: locForOk);
1252 })
1253 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusOrMemberCallWithName(member_name: "ok"),
1254 A: transferStatusOrOkCall)
1255 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusOrMemberCallWithName(member_name: "status"),
1256 A: transferStatusCall)
1257 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusMemberCallWithName(member_name: "ok"),
1258 A: transferStatusOkCall)
1259 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusMemberCallWithName(member_name: "Update"),
1260 A: transferStatusUpdateCall)
1261 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1262 M: isComparisonOperatorCall(operator_name: "=="),
1263 A: [](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
1264 LatticeTransferState &State) {
1265 transferComparisonOperator(Expr, State,
1266 /*IsNegative=*/false);
1267 })
1268 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1269 M: isComparisonOperatorCall(operator_name: "!="),
1270 A: [](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
1271 LatticeTransferState &State) {
1272 transferComparisonOperator(Expr, State,
1273 /*IsNegative=*/true);
1274 })
1275 .CaseOfCFGStmt<BinaryOperator>(
1276 M: isPointerComparisonOperatorCall(operator_name: "=="),
1277 A: [](const BinaryOperator *Expr, const MatchFinder::MatchResult &,
1278 LatticeTransferState &State) {
1279 transferPointerComparisonOperator(Expr, State,
1280 /*IsNegative=*/false);
1281 })
1282 .CaseOfCFGStmt<BinaryOperator>(
1283 M: isPointerComparisonOperatorCall(operator_name: "!="),
1284 A: [](const BinaryOperator *Expr, const MatchFinder::MatchResult &,
1285 LatticeTransferState &State) {
1286 transferPointerComparisonOperator(Expr, State,
1287 /*IsNegative=*/true);
1288 })
1289 .CaseOfCFGStmt<CallExpr>(M: isOkStatusCall(), A: transferOkStatusCall)
1290 .CaseOfCFGStmt<CallExpr>(M: isNotOkStatusCall(), A: transferNotOkStatusCall)
1291 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusOrMemberCallWithName(member_name: "emplace"),
1292 A: transferEmplaceCall)
1293 .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isStatusOrValueAssignmentCall(),
1294 A: transferValueAssignmentCall)
1295 .CaseOfCFGStmt<CXXConstructExpr>(M: isStatusOrValueConstructor(),
1296 A: transferValueConstructor)
1297 .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isStatusOrOperatorCallWithName(operator_name: "->"),
1298 A: transferArrowCall)
1299 .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isStatusOrOperatorCallWithName(operator_name: "*"),
1300 A: transferDerefCall)
1301 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isStatusOrMemberCallWithName(member_name: "value"),
1302 A: transferValueCall)
1303 .CaseOfCFGStmt<CallExpr>(M: isAsStatusCallWithStatus(),
1304 A: transferAsStatusCallWithStatus)
1305 .CaseOfCFGStmt<CallExpr>(M: isAsStatusCallWithStatusOr(),
1306 A: transferAsStatusCallWithStatusOr)
1307 .CaseOfCFGStmt<CallExpr>(M: isLoggingGetReferenceableValueCall(),
1308 A: transferLoggingGetReferenceableValueCall)
1309 .CaseOfCFGStmt<CallExpr>(M: isLoggingCheckEqImpl(),
1310 A: transferLoggingCheckEqImpl)
1311 // This needs to go before the const accessor call matcher, because these
1312 // look like them, but we model `operator`* and `get` to return the same
1313 // object. Also, we model them for non-const cases.
1314 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1315 M: isPointerLikeOperatorStar(),
1316 A: [](const CXXOperatorCallExpr *E,
1317 const MatchFinder::MatchResult &Result,
1318 LatticeTransferState &State) {
1319 transferSmartPointerLikeCachedDeref(
1320 DerefExpr: E, SmartPointerLoc: getSmartPtrLikeStorageLocation(E: *E->getArg(Arg: 0), Env: State.Env),
1321 State, InitializeLoc: [](StorageLocation &Loc) {});
1322 })
1323 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1324 M: isPointerLikeOperatorArrow(),
1325 A: [](const CXXOperatorCallExpr *E,
1326 const MatchFinder::MatchResult &Result,
1327 LatticeTransferState &State) {
1328 transferSmartPointerLikeCachedGet(
1329 GetExpr: E, SmartPointerLoc: getSmartPtrLikeStorageLocation(E: *E->getArg(Arg: 0), Env: State.Env),
1330 State, InitializeLoc: [](StorageLocation &Loc) {});
1331 })
1332 .CaseOfCFGStmt<CXXMemberCallExpr>(
1333 M: isSmartPointerLikeValueMethodCall(),
1334 A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
1335 LatticeTransferState &State) {
1336 transferSmartPointerLikeCachedDeref(
1337 DerefExpr: E, SmartPointerLoc: getImplicitObjectLocation(MCE: *E, Env: State.Env), State,
1338 InitializeLoc: [](StorageLocation &Loc) {});
1339 })
1340 .CaseOfCFGStmt<CXXMemberCallExpr>(
1341 M: isSmartPointerLikeGetMethodCall(),
1342 A: [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
1343 LatticeTransferState &State) {
1344 transferSmartPointerLikeCachedGet(
1345 GetExpr: E, SmartPointerLoc: getImplicitObjectLocation(MCE: *E, Env: State.Env), State,
1346 InitializeLoc: [](StorageLocation &Loc) {});
1347 })
1348 // const accessor calls
1349 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isConstAccessorMemberCall(),
1350 A: transferConstAccessorMemberCall)
1351 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1352 M: isConstAccessorMemberOperatorCall(),
1353 A: transferConstAccessorMemberOperatorCall)
1354 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isConstPointerAccessorMemberCall(),
1355 A: transferConstPointerAccessorMemberCall)
1356 .CaseOfCFGStmt<CXXOperatorCallExpr>(
1357 M: isConstPointerAccessorMemberOperatorCall(),
1358 A: transferConstPointerAccessorMemberOperatorCall)
1359 // non-const member calls that may modify the state of an object.
1360 .CaseOfCFGStmt<CXXMemberCallExpr>(M: isNonConstMemberCall(),
1361 A: transferNonConstMemberCall)
1362 .CaseOfCFGStmt<CXXOperatorCallExpr>(M: isNonConstMemberOperatorCall(),
1363 A: transferNonConstMemberOperatorCall)
1364 // N.B. this has to be after transferConstMemberCall, otherwise we would
1365 // always return a fresh RecordStorageLocation for the StatusOr.
1366 .CaseOfCFGStmt<CallExpr>(M: isStatusOrReturningCall(),
1367 A: [](const CallExpr *Expr,
1368 const MatchFinder::MatchResult &,
1369 LatticeTransferState &State) {
1370 transferStatusOrReturningCall(Expr, State);
1371 })
1372 .CaseOfCFGStmt<CallExpr>(M: isStatusOrPtrReturningCall(),
1373 A: transferStatusOrPtrReturningCall)
1374 .CaseOfCFGStmt<CallExpr>(M: isStatusPtrReturningCall(),
1375 A: transferStatusPtrReturningCall)
1376 // N.B. These need to come after all other CXXConstructExpr.
1377 // These are there to make sure that every Status and StatusOr object
1378 // have their ok boolean initialized when constructed. If we were to
1379 // lazily initialize them when we first access them, we can produce
1380 // false positives if that first access is in a control flow statement.
1381 // You can comment out these two constructors and see tests fail.
1382 .CaseOfCFGStmt<CXXConstructExpr>(M: isStatusOrConstructor(),
1383 A: transferStatusOrConstructor)
1384 .CaseOfCFGStmt<CXXConstructExpr>(M: isStatusConstructor(),
1385 A: transferStatusConstructor)
1386 .CaseOfCFGStmt<ImplicitCastExpr>(
1387 M: implicitCastExpr(hasCastKind(Kind: CK_PointerToBoolean)),
1388 A: transferPointerToBoolean)
1389 .CaseOfCFGStmt<clang::CXXMemberCallExpr>(
1390 M: isReturnIfErrorAdaptorOperatorBoolCall(),
1391 A: transferReturnIfErrorAdaptorOperatorBoolCall)
1392 .CaseOfCFGStmt<clang::CallExpr>(M: isMacroAdaptorCall(),
1393 A: transferMacroAdaptorCall)
1394 .Build();
1395}
1396
1397QualType findStatusType(const ASTContext &Ctx) {
1398 for (Type *Ty : Ctx.getTypes())
1399 if (isStatusType(Type: QualType(Ty, 0)))
1400 return QualType(Ty, 0);
1401
1402 return QualType();
1403}
1404
1405UncheckedStatusOrAccessModel::UncheckedStatusOrAccessModel(ASTContext &Ctx,
1406 Environment &Env)
1407 : DataflowAnalysis<UncheckedStatusOrAccessModel,
1408 UncheckedStatusOrAccessModel::Lattice>(Ctx),
1409 TransferMatchSwitch(buildTransferMatchSwitch(Ctx, Builder: {})) {
1410 QualType StatusType = findStatusType(Ctx);
1411 Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
1412 [StatusType](QualType Ty) -> llvm::StringMap<QualType> {
1413 CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
1414 if (RD == nullptr)
1415 return {};
1416
1417 if (auto Fields = getSyntheticFields(Ty, StatusType, RD: *RD);
1418 !Fields.empty())
1419 return Fields;
1420 return {};
1421 });
1422}
1423
1424void UncheckedStatusOrAccessModel::transfer(const CFGElement &Elt, Lattice &L,
1425 Environment &Env) {
1426 LatticeTransferState State(L, Env);
1427 TransferMatchSwitch(Elt, getASTContext(), State);
1428}
1429
1430} // namespace clang::dataflow::statusor_model
1431