xref: /llvm-project/clang/lib/Analysis/FlowSensitive/ASTOps.cpp (revision 198fb5ed4ac7d096da03ea4a0a27832d18b1350f)
19ec8c961SSamira Bazuzi //===-- ASTOps.cc -------------------------------*- C++ -*-===//
29ec8c961SSamira Bazuzi //
39ec8c961SSamira Bazuzi // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
49ec8c961SSamira Bazuzi // See https://llvm.org/LICENSE.txt for license information.
59ec8c961SSamira Bazuzi // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
69ec8c961SSamira Bazuzi //
79ec8c961SSamira Bazuzi //===----------------------------------------------------------------------===//
89ec8c961SSamira Bazuzi //
99ec8c961SSamira Bazuzi //  Operations on AST nodes that are used in flow-sensitive analysis.
109ec8c961SSamira Bazuzi //
119ec8c961SSamira Bazuzi //===----------------------------------------------------------------------===//
129ec8c961SSamira Bazuzi 
139ec8c961SSamira Bazuzi #include "clang/Analysis/FlowSensitive/ASTOps.h"
14*198fb5edSSamira Bazuzi #include "clang/AST/ASTLambda.h"
159ec8c961SSamira Bazuzi #include "clang/AST/ComputeDependence.h"
169ec8c961SSamira Bazuzi #include "clang/AST/Decl.h"
179ec8c961SSamira Bazuzi #include "clang/AST/DeclBase.h"
189ec8c961SSamira Bazuzi #include "clang/AST/DeclCXX.h"
199ec8c961SSamira Bazuzi #include "clang/AST/Expr.h"
209ec8c961SSamira Bazuzi #include "clang/AST/ExprCXX.h"
219ec8c961SSamira Bazuzi #include "clang/AST/Stmt.h"
229ec8c961SSamira Bazuzi #include "clang/AST/Type.h"
239ec8c961SSamira Bazuzi #include "clang/Analysis/FlowSensitive/StorageLocation.h"
249ec8c961SSamira Bazuzi #include "clang/Basic/LLVM.h"
259ec8c961SSamira Bazuzi #include "llvm/ADT/DenseSet.h"
269ec8c961SSamira Bazuzi #include "llvm/ADT/STLExtras.h"
279ec8c961SSamira Bazuzi #include <cassert>
289ec8c961SSamira Bazuzi #include <iterator>
299ec8c961SSamira Bazuzi #include <vector>
309ec8c961SSamira Bazuzi 
319ec8c961SSamira Bazuzi #define DEBUG_TYPE "dataflow"
329ec8c961SSamira Bazuzi 
339ec8c961SSamira Bazuzi namespace clang::dataflow {
349ec8c961SSamira Bazuzi 
359ec8c961SSamira Bazuzi const Expr &ignoreCFGOmittedNodes(const Expr &E) {
369ec8c961SSamira Bazuzi   const Expr *Current = &E;
37c70f0583Smartinboehme   const Expr *Last = nullptr;
38c70f0583Smartinboehme   while (Current != Last) {
39c70f0583Smartinboehme     Last = Current;
409ec8c961SSamira Bazuzi     if (auto *EWC = dyn_cast<ExprWithCleanups>(Current)) {
419ec8c961SSamira Bazuzi       Current = EWC->getSubExpr();
429ec8c961SSamira Bazuzi       assert(Current != nullptr);
439ec8c961SSamira Bazuzi     }
44c70f0583Smartinboehme     if (auto *CE = dyn_cast<ConstantExpr>(Current)) {
45c70f0583Smartinboehme       Current = CE->getSubExpr();
46c70f0583Smartinboehme       assert(Current != nullptr);
47c70f0583Smartinboehme     }
489ec8c961SSamira Bazuzi     Current = Current->IgnoreParens();
499ec8c961SSamira Bazuzi     assert(Current != nullptr);
50c70f0583Smartinboehme   }
519ec8c961SSamira Bazuzi   return *Current;
529ec8c961SSamira Bazuzi }
539ec8c961SSamira Bazuzi 
549ec8c961SSamira Bazuzi const Stmt &ignoreCFGOmittedNodes(const Stmt &S) {
559ec8c961SSamira Bazuzi   if (auto *E = dyn_cast<Expr>(&S))
569ec8c961SSamira Bazuzi     return ignoreCFGOmittedNodes(*E);
579ec8c961SSamira Bazuzi   return S;
589ec8c961SSamira Bazuzi }
599ec8c961SSamira Bazuzi 
609ec8c961SSamira Bazuzi // FIXME: Does not precisely handle non-virtual diamond inheritance. A single
619ec8c961SSamira Bazuzi // field decl will be modeled for all instances of the inherited field.
629ec8c961SSamira Bazuzi static void getFieldsFromClassHierarchy(QualType Type, FieldSet &Fields) {
639ec8c961SSamira Bazuzi   if (Type->isIncompleteType() || Type->isDependentType() ||
649ec8c961SSamira Bazuzi       !Type->isRecordType())
659ec8c961SSamira Bazuzi     return;
669ec8c961SSamira Bazuzi 
679ec8c961SSamira Bazuzi   for (const FieldDecl *Field : Type->getAsRecordDecl()->fields())
689ec8c961SSamira Bazuzi     Fields.insert(Field);
699ec8c961SSamira Bazuzi   if (auto *CXXRecord = Type->getAsCXXRecordDecl())
709ec8c961SSamira Bazuzi     for (const CXXBaseSpecifier &Base : CXXRecord->bases())
719ec8c961SSamira Bazuzi       getFieldsFromClassHierarchy(Base.getType(), Fields);
729ec8c961SSamira Bazuzi }
739ec8c961SSamira Bazuzi 
749ec8c961SSamira Bazuzi /// Gets the set of all fields in the type.
759ec8c961SSamira Bazuzi FieldSet getObjectFields(QualType Type) {
769ec8c961SSamira Bazuzi   FieldSet Fields;
779ec8c961SSamira Bazuzi   getFieldsFromClassHierarchy(Type, Fields);
789ec8c961SSamira Bazuzi   return Fields;
799ec8c961SSamira Bazuzi }
809ec8c961SSamira Bazuzi 
819ec8c961SSamira Bazuzi bool containsSameFields(const FieldSet &Fields,
829ec8c961SSamira Bazuzi                         const RecordStorageLocation::FieldToLoc &FieldLocs) {
839ec8c961SSamira Bazuzi   if (Fields.size() != FieldLocs.size())
849ec8c961SSamira Bazuzi     return false;
859ec8c961SSamira Bazuzi   for ([[maybe_unused]] auto [Field, Loc] : FieldLocs)
869ec8c961SSamira Bazuzi     if (!Fields.contains(cast_or_null<FieldDecl>(Field)))
879ec8c961SSamira Bazuzi       return false;
889ec8c961SSamira Bazuzi   return true;
899ec8c961SSamira Bazuzi }
909ec8c961SSamira Bazuzi 
919ec8c961SSamira Bazuzi /// Returns the fields of a `RecordDecl` that are initialized by an
92ca7d9442Smartinboehme /// `InitListExpr` or `CXXParenListInitExpr`, in the order in which they appear
93ca7d9442Smartinboehme /// in `InitListExpr::inits()` / `CXXParenListInitExpr::getInitExprs()`.
94ca7d9442Smartinboehme /// `InitList->getType()` must be a record type.
95ca7d9442Smartinboehme template <class InitListT>
969ec8c961SSamira Bazuzi static std::vector<const FieldDecl *>
97ca7d9442Smartinboehme getFieldsForInitListExpr(const InitListT *InitList) {
989ec8c961SSamira Bazuzi   const RecordDecl *RD = InitList->getType()->getAsRecordDecl();
999ec8c961SSamira Bazuzi   assert(RD != nullptr);
1009ec8c961SSamira Bazuzi 
1019ec8c961SSamira Bazuzi   std::vector<const FieldDecl *> Fields;
1029ec8c961SSamira Bazuzi 
1039ec8c961SSamira Bazuzi   if (InitList->getType()->isUnionType()) {
104275196d8Smartinboehme     if (const FieldDecl *Field = InitList->getInitializedFieldInUnion())
105275196d8Smartinboehme       Fields.push_back(Field);
1069ec8c961SSamira Bazuzi     return Fields;
1079ec8c961SSamira Bazuzi   }
1089ec8c961SSamira Bazuzi 
1099ec8c961SSamira Bazuzi   // Unnamed bitfields are only used for padding and do not appear in
1109ec8c961SSamira Bazuzi   // `InitListExpr`'s inits. However, those fields do appear in `RecordDecl`'s
1119ec8c961SSamira Bazuzi   // field list, and we thus need to remove them before mapping inits to
1129ec8c961SSamira Bazuzi   // fields to avoid mapping inits to the wrongs fields.
1139ec8c961SSamira Bazuzi   llvm::copy_if(
1149ec8c961SSamira Bazuzi       RD->fields(), std::back_inserter(Fields),
1153d56ea05STimm Baeder       [](const FieldDecl *Field) { return !Field->isUnnamedBitField(); });
1169ec8c961SSamira Bazuzi   return Fields;
1179ec8c961SSamira Bazuzi }
1189ec8c961SSamira Bazuzi 
119ca7d9442Smartinboehme RecordInitListHelper::RecordInitListHelper(const InitListExpr *InitList)
120ca7d9442Smartinboehme     : RecordInitListHelper(InitList->getType(),
121ca7d9442Smartinboehme                            getFieldsForInitListExpr(InitList),
122ca7d9442Smartinboehme                            InitList->inits()) {}
1239ec8c961SSamira Bazuzi 
124ca7d9442Smartinboehme RecordInitListHelper::RecordInitListHelper(
125ca7d9442Smartinboehme     const CXXParenListInitExpr *ParenInitList)
126ca7d9442Smartinboehme     : RecordInitListHelper(ParenInitList->getType(),
127ca7d9442Smartinboehme                            getFieldsForInitListExpr(ParenInitList),
128ca7d9442Smartinboehme                            ParenInitList->getInitExprs()) {}
129ca7d9442Smartinboehme 
130ca7d9442Smartinboehme RecordInitListHelper::RecordInitListHelper(
131ca7d9442Smartinboehme     QualType Ty, std::vector<const FieldDecl *> Fields,
132ca7d9442Smartinboehme     ArrayRef<Expr *> Inits) {
133ca7d9442Smartinboehme   auto *RD = Ty->getAsCXXRecordDecl();
134ca7d9442Smartinboehme   assert(RD != nullptr);
1359ec8c961SSamira Bazuzi 
1369ec8c961SSamira Bazuzi   // Unions initialized with an empty initializer list need special treatment.
1379ec8c961SSamira Bazuzi   // For structs/classes initialized with an empty initializer list, Clang
1389ec8c961SSamira Bazuzi   // puts `ImplicitValueInitExpr`s in `InitListExpr::inits()`, but for unions,
1399ec8c961SSamira Bazuzi   // it doesn't do this -- so we create an `ImplicitValueInitExpr` ourselves.
1409ec8c961SSamira Bazuzi   SmallVector<Expr *> InitsForUnion;
141ca7d9442Smartinboehme   if (Ty->isUnionType() && Inits.empty()) {
142275196d8Smartinboehme     assert(Fields.size() <= 1);
143275196d8Smartinboehme     if (!Fields.empty()) {
1449ec8c961SSamira Bazuzi       ImplicitValueInitForUnion.emplace(Fields.front()->getType());
1459ec8c961SSamira Bazuzi       InitsForUnion.push_back(&*ImplicitValueInitForUnion);
146275196d8Smartinboehme     }
1479ec8c961SSamira Bazuzi     Inits = InitsForUnion;
1489ec8c961SSamira Bazuzi   }
1499ec8c961SSamira Bazuzi 
1509ec8c961SSamira Bazuzi   size_t InitIdx = 0;
1519ec8c961SSamira Bazuzi 
1529ec8c961SSamira Bazuzi   assert(Fields.size() + RD->getNumBases() == Inits.size());
1539ec8c961SSamira Bazuzi   for (const CXXBaseSpecifier &Base : RD->bases()) {
1549ec8c961SSamira Bazuzi     assert(InitIdx < Inits.size());
1559ec8c961SSamira Bazuzi     Expr *Init = Inits[InitIdx++];
1569ec8c961SSamira Bazuzi     BaseInits.emplace_back(&Base, Init);
1579ec8c961SSamira Bazuzi   }
1589ec8c961SSamira Bazuzi 
1599ec8c961SSamira Bazuzi   assert(Fields.size() == Inits.size() - InitIdx);
1609ec8c961SSamira Bazuzi   for (const FieldDecl *Field : Fields) {
1619ec8c961SSamira Bazuzi     assert(InitIdx < Inits.size());
1629ec8c961SSamira Bazuzi     Expr *Init = Inits[InitIdx++];
1639ec8c961SSamira Bazuzi     FieldInits.emplace_back(Field, Init);
1649ec8c961SSamira Bazuzi   }
1659ec8c961SSamira Bazuzi }
1669ec8c961SSamira Bazuzi 
1679ec8c961SSamira Bazuzi static void insertIfGlobal(const Decl &D,
1689ec8c961SSamira Bazuzi                            llvm::DenseSet<const VarDecl *> &Globals) {
1699ec8c961SSamira Bazuzi   if (auto *V = dyn_cast<VarDecl>(&D))
1709ec8c961SSamira Bazuzi     if (V->hasGlobalStorage())
1719ec8c961SSamira Bazuzi       Globals.insert(V);
1729ec8c961SSamira Bazuzi }
1739ec8c961SSamira Bazuzi 
1742575ea6eSSamira Bazuzi static void insertIfLocal(const Decl &D,
1752575ea6eSSamira Bazuzi                           llvm::DenseSet<const VarDecl *> &Locals) {
1762575ea6eSSamira Bazuzi   if (auto *V = dyn_cast<VarDecl>(&D))
1772575ea6eSSamira Bazuzi     if (V->hasLocalStorage() && !isa<ParmVarDecl>(V))
1782575ea6eSSamira Bazuzi       Locals.insert(V);
1792575ea6eSSamira Bazuzi }
1802575ea6eSSamira Bazuzi 
1819ec8c961SSamira Bazuzi static void insertIfFunction(const Decl &D,
1829ec8c961SSamira Bazuzi                              llvm::DenseSet<const FunctionDecl *> &Funcs) {
1839ec8c961SSamira Bazuzi   if (auto *FD = dyn_cast<FunctionDecl>(&D))
1849ec8c961SSamira Bazuzi     Funcs.insert(FD);
1859ec8c961SSamira Bazuzi }
1869ec8c961SSamira Bazuzi 
1879ec8c961SSamira Bazuzi static MemberExpr *getMemberForAccessor(const CXXMemberCallExpr &C) {
1889ec8c961SSamira Bazuzi   // Use getCalleeDecl instead of getMethodDecl in order to handle
1899ec8c961SSamira Bazuzi   // pointer-to-member calls.
1909ec8c961SSamira Bazuzi   const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(C.getCalleeDecl());
1919ec8c961SSamira Bazuzi   if (!MethodDecl)
1929ec8c961SSamira Bazuzi     return nullptr;
1939ec8c961SSamira Bazuzi   auto *Body = dyn_cast_or_null<CompoundStmt>(MethodDecl->getBody());
1949ec8c961SSamira Bazuzi   if (!Body || Body->size() != 1)
1959ec8c961SSamira Bazuzi     return nullptr;
1969ec8c961SSamira Bazuzi   if (auto *RS = dyn_cast<ReturnStmt>(*Body->body_begin()))
1979ec8c961SSamira Bazuzi     if (auto *Return = RS->getRetValue())
1989ec8c961SSamira Bazuzi       return dyn_cast<MemberExpr>(Return->IgnoreParenImpCasts());
1999ec8c961SSamira Bazuzi   return nullptr;
2009ec8c961SSamira Bazuzi }
2019ec8c961SSamira Bazuzi 
202dde802b1SSirraide class ReferencedDeclsVisitor : public AnalysisASTVisitor {
2035161a3f6Smartinboehme public:
2045161a3f6Smartinboehme   ReferencedDeclsVisitor(ReferencedDecls &Referenced)
2055161a3f6Smartinboehme       : Referenced(Referenced) {}
2065161a3f6Smartinboehme 
207dde802b1SSirraide   void traverseConstructorInits(const CXXConstructorDecl *Ctor) {
2085161a3f6Smartinboehme     for (const CXXCtorInitializer *Init : Ctor->inits()) {
2095161a3f6Smartinboehme       if (Init->isMemberInitializer()) {
2105161a3f6Smartinboehme         Referenced.Fields.insert(Init->getMember());
2115161a3f6Smartinboehme       } else if (Init->isIndirectMemberInitializer()) {
2125161a3f6Smartinboehme         for (const auto *I : Init->getIndirectMember()->chain())
2135161a3f6Smartinboehme           Referenced.Fields.insert(cast<FieldDecl>(I));
2149ec8c961SSamira Bazuzi       }
2159ec8c961SSamira Bazuzi 
2165161a3f6Smartinboehme       Expr *InitExpr = Init->getInit();
2179ec8c961SSamira Bazuzi 
2185161a3f6Smartinboehme       // Also collect declarations referenced in `InitExpr`.
2195161a3f6Smartinboehme       TraverseStmt(InitExpr);
2205161a3f6Smartinboehme 
2215161a3f6Smartinboehme       // If this is a `CXXDefaultInitExpr`, also collect declarations referenced
2225161a3f6Smartinboehme       // within the default expression.
2235161a3f6Smartinboehme       if (auto *DefaultInit = dyn_cast<CXXDefaultInitExpr>(InitExpr))
2245161a3f6Smartinboehme         TraverseStmt(DefaultInit->getExpr());
2255161a3f6Smartinboehme     }
2265161a3f6Smartinboehme   }
2275161a3f6Smartinboehme 
228dde802b1SSirraide   bool VisitDecl(Decl *D) override {
2295161a3f6Smartinboehme     insertIfGlobal(*D, Referenced.Globals);
2302575ea6eSSamira Bazuzi     insertIfLocal(*D, Referenced.Locals);
2315161a3f6Smartinboehme     insertIfFunction(*D, Referenced.Functions);
2325161a3f6Smartinboehme     return true;
2335161a3f6Smartinboehme   }
2345161a3f6Smartinboehme 
235dde802b1SSirraide   bool VisitDeclRefExpr(DeclRefExpr *E) override {
2369ec8c961SSamira Bazuzi     insertIfGlobal(*E->getDecl(), Referenced.Globals);
2372575ea6eSSamira Bazuzi     insertIfLocal(*E->getDecl(), Referenced.Locals);
2389ec8c961SSamira Bazuzi     insertIfFunction(*E->getDecl(), Referenced.Functions);
2395161a3f6Smartinboehme     return true;
2405161a3f6Smartinboehme   }
2415161a3f6Smartinboehme 
242dde802b1SSirraide   bool VisitCXXMemberCallExpr(CXXMemberCallExpr *C) override {
2439ec8c961SSamira Bazuzi     // If this is a method that returns a member variable but does nothing else,
2449ec8c961SSamira Bazuzi     // model the field of the return value.
2459ec8c961SSamira Bazuzi     if (MemberExpr *E = getMemberForAccessor(*C))
2469ec8c961SSamira Bazuzi       if (const auto *FD = dyn_cast<FieldDecl>(E->getMemberDecl()))
2479ec8c961SSamira Bazuzi         Referenced.Fields.insert(FD);
2485161a3f6Smartinboehme     return true;
2495161a3f6Smartinboehme   }
2505161a3f6Smartinboehme 
251dde802b1SSirraide   bool VisitMemberExpr(MemberExpr *E) override {
2529ec8c961SSamira Bazuzi     // FIXME: should we be using `E->getFoundDecl()`?
2539ec8c961SSamira Bazuzi     const ValueDecl *VD = E->getMemberDecl();
2549ec8c961SSamira Bazuzi     insertIfGlobal(*VD, Referenced.Globals);
2559ec8c961SSamira Bazuzi     insertIfFunction(*VD, Referenced.Functions);
2569ec8c961SSamira Bazuzi     if (const auto *FD = dyn_cast<FieldDecl>(VD))
2579ec8c961SSamira Bazuzi       Referenced.Fields.insert(FD);
2585161a3f6Smartinboehme     return true;
2595161a3f6Smartinboehme   }
2605161a3f6Smartinboehme 
261dde802b1SSirraide   bool VisitInitListExpr(InitListExpr *InitList) override {
2629ec8c961SSamira Bazuzi     if (InitList->getType()->isRecordType())
2639ec8c961SSamira Bazuzi       for (const auto *FD : getFieldsForInitListExpr(InitList))
2649ec8c961SSamira Bazuzi         Referenced.Fields.insert(FD);
2655161a3f6Smartinboehme     return true;
2665161a3f6Smartinboehme   }
2675161a3f6Smartinboehme 
268dde802b1SSirraide   bool VisitCXXParenListInitExpr(CXXParenListInitExpr *ParenInitList) override {
269ca7d9442Smartinboehme     if (ParenInitList->getType()->isRecordType())
270ca7d9442Smartinboehme       for (const auto *FD : getFieldsForInitListExpr(ParenInitList))
271ca7d9442Smartinboehme         Referenced.Fields.insert(FD);
2725161a3f6Smartinboehme     return true;
2739ec8c961SSamira Bazuzi   }
2745161a3f6Smartinboehme 
2755161a3f6Smartinboehme private:
2765161a3f6Smartinboehme   ReferencedDecls &Referenced;
2775161a3f6Smartinboehme };
2789ec8c961SSamira Bazuzi 
2799ec8c961SSamira Bazuzi ReferencedDecls getReferencedDecls(const FunctionDecl &FD) {
2809ec8c961SSamira Bazuzi   ReferencedDecls Result;
2815161a3f6Smartinboehme   ReferencedDeclsVisitor Visitor(Result);
2825161a3f6Smartinboehme   Visitor.TraverseStmt(FD.getBody());
2835161a3f6Smartinboehme   if (const auto *CtorDecl = dyn_cast<CXXConstructorDecl>(&FD))
284dde802b1SSirraide     Visitor.traverseConstructorInits(CtorDecl);
2859ec8c961SSamira Bazuzi 
286*198fb5edSSamira Bazuzi   // If analyzing a lambda call operator, collect all captures of parameters (of
287*198fb5edSSamira Bazuzi   // the surrounding function). This collects them even if they are not
288*198fb5edSSamira Bazuzi   // referenced in the body of the lambda call operator. Non-parameter local
289*198fb5edSSamira Bazuzi   // variables that are captured are already collected into
290*198fb5edSSamira Bazuzi   // `ReferencedDecls.Locals` when traversing the call operator body, but we
291*198fb5edSSamira Bazuzi   // collect parameters here to avoid needing to check at each referencing node
292*198fb5edSSamira Bazuzi   // whether the parameter is a lambda capture from a surrounding function or is
293*198fb5edSSamira Bazuzi   // a parameter of the current function. If it becomes necessary to limit this
294*198fb5edSSamira Bazuzi   // set to the parameters actually referenced in the body, alternative
295*198fb5edSSamira Bazuzi   // optimizations can be implemented to minimize duplicative work.
296*198fb5edSSamira Bazuzi   if (const auto *Method = dyn_cast<CXXMethodDecl>(&FD);
297*198fb5edSSamira Bazuzi       Method && isLambdaCallOperator(Method)) {
298*198fb5edSSamira Bazuzi     for (const auto &Capture : Method->getParent()->captures()) {
299*198fb5edSSamira Bazuzi       if (Capture.capturesVariable()) {
300*198fb5edSSamira Bazuzi         if (const auto *Param =
301*198fb5edSSamira Bazuzi                 dyn_cast<ParmVarDecl>(Capture.getCapturedVar())) {
302*198fb5edSSamira Bazuzi           Result.LambdaCapturedParams.insert(Param);
303*198fb5edSSamira Bazuzi         }
304*198fb5edSSamira Bazuzi       }
305*198fb5edSSamira Bazuzi     }
306*198fb5edSSamira Bazuzi   }
307*198fb5edSSamira Bazuzi 
3089ec8c961SSamira Bazuzi   return Result;
3099ec8c961SSamira Bazuzi }
3109ec8c961SSamira Bazuzi 
311d634b233SSamira Bazuzi ReferencedDecls getReferencedDecls(const Stmt &S) {
312d634b233SSamira Bazuzi   ReferencedDecls Result;
3135161a3f6Smartinboehme   ReferencedDeclsVisitor Visitor(Result);
3145161a3f6Smartinboehme   Visitor.TraverseStmt(const_cast<Stmt *>(&S));
315d634b233SSamira Bazuzi   return Result;
316d634b233SSamira Bazuzi }
317d634b233SSamira Bazuzi 
3189ec8c961SSamira Bazuzi } // namespace clang::dataflow
319