1ed8f7882SAaron Ballman //===--- DirectiveTree.cpp - Find and strip preprocessor directives -------===// 2ed8f7882SAaron Ballman // 3ed8f7882SAaron Ballman // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4ed8f7882SAaron Ballman // See https://llvm.org/LICENSE.txt for license information. 5ed8f7882SAaron Ballman // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6ed8f7882SAaron Ballman // 7ed8f7882SAaron Ballman //===----------------------------------------------------------------------===// 8ed8f7882SAaron Ballman 9ed8f7882SAaron Ballman #include "DirectiveTree.h" 10ed8f7882SAaron Ballman #include "clang/Basic/IdentifierTable.h" 11ed8f7882SAaron Ballman #include "clang/Basic/TokenKinds.h" 12ed8f7882SAaron Ballman #include "llvm/Support/FormatVariadic.h" 13ed8f7882SAaron Ballman #include <optional> 14ed8f7882SAaron Ballman #include <variant> 15ed8f7882SAaron Ballman 16ed8f7882SAaron Ballman namespace clang { 17ed8f7882SAaron Ballman namespace clangd { 18ed8f7882SAaron Ballman namespace { 19ed8f7882SAaron Ballman 20ed8f7882SAaron Ballman class DirectiveParser { 21ed8f7882SAaron Ballman public: 22ed8f7882SAaron Ballman explicit DirectiveParser(const TokenStream &Code) 23ed8f7882SAaron Ballman : Code(Code), Tok(&Code.front()) {} 24ed8f7882SAaron Ballman void parse(DirectiveTree *Result) { parse(Result, /*TopLevel=*/true); } 25ed8f7882SAaron Ballman 26ed8f7882SAaron Ballman private: 27ed8f7882SAaron Ballman // Roles that a directive might take within a conditional block. 28ed8f7882SAaron Ballman enum class Cond { None, If, Else, End }; 29ed8f7882SAaron Ballman static Cond classifyDirective(tok::PPKeywordKind K) { 30ed8f7882SAaron Ballman switch (K) { 31ed8f7882SAaron Ballman case clang::tok::pp_if: 32ed8f7882SAaron Ballman case clang::tok::pp_ifdef: 33ed8f7882SAaron Ballman case clang::tok::pp_ifndef: 34ed8f7882SAaron Ballman return Cond::If; 35ed8f7882SAaron Ballman case clang::tok::pp_elif: 36ed8f7882SAaron Ballman case clang::tok::pp_elifdef: 37ed8f7882SAaron Ballman case clang::tok::pp_elifndef: 38ed8f7882SAaron Ballman case clang::tok::pp_else: 39ed8f7882SAaron Ballman return Cond::Else; 40ed8f7882SAaron Ballman case clang::tok::pp_endif: 41ed8f7882SAaron Ballman return Cond::End; 42ed8f7882SAaron Ballman default: 43ed8f7882SAaron Ballman return Cond::None; 44ed8f7882SAaron Ballman } 45ed8f7882SAaron Ballman } 46ed8f7882SAaron Ballman 47ed8f7882SAaron Ballman // Parses tokens starting at Tok into Tree. 48ed8f7882SAaron Ballman // If we reach an End or Else directive that ends Tree, returns it. 49ed8f7882SAaron Ballman // If TopLevel is true, then we do not expect End and always return 50ed8f7882SAaron Ballman // std::nullopt. 51ed8f7882SAaron Ballman std::optional<DirectiveTree::Directive> parse(DirectiveTree *Tree, 52ed8f7882SAaron Ballman bool TopLevel) { 53ed8f7882SAaron Ballman auto StartsDirective = 54ed8f7882SAaron Ballman [&, AllowDirectiveAt((const Token *)nullptr)]() mutable { 55ed8f7882SAaron Ballman if (Tok->flag(LexFlags::StartsPPLine)) { 56ed8f7882SAaron Ballman // If we considered a comment at the start of a PP-line, it doesn't 57ed8f7882SAaron Ballman // start a directive but the directive can still start after it. 58ed8f7882SAaron Ballman if (Tok->Kind == tok::comment) 59ed8f7882SAaron Ballman AllowDirectiveAt = Tok + 1; 60ed8f7882SAaron Ballman return Tok->Kind == tok::hash; 61ed8f7882SAaron Ballman } 62ed8f7882SAaron Ballman return Tok->Kind == tok::hash && AllowDirectiveAt == Tok; 63ed8f7882SAaron Ballman }; 64ed8f7882SAaron Ballman // Each iteration adds one chunk (or returns, if we see #endif). 65ed8f7882SAaron Ballman while (Tok->Kind != tok::eof) { 66ed8f7882SAaron Ballman // If there's no directive here, we have a code chunk. 67ed8f7882SAaron Ballman if (!StartsDirective()) { 68ed8f7882SAaron Ballman const Token *Start = Tok; 69ed8f7882SAaron Ballman do 70ed8f7882SAaron Ballman ++Tok; 71ed8f7882SAaron Ballman while (Tok->Kind != tok::eof && !StartsDirective()); 72ed8f7882SAaron Ballman Tree->Chunks.push_back(DirectiveTree::Code{ 73ed8f7882SAaron Ballman Token::Range{Code.index(*Start), Code.index(*Tok)}}); 74ed8f7882SAaron Ballman continue; 75ed8f7882SAaron Ballman } 76ed8f7882SAaron Ballman 77ed8f7882SAaron Ballman // We have some kind of directive. 78ed8f7882SAaron Ballman DirectiveTree::Directive Directive; 79ed8f7882SAaron Ballman parseDirective(&Directive); 80ed8f7882SAaron Ballman Cond Kind = classifyDirective(Directive.Kind); 81ed8f7882SAaron Ballman if (Kind == Cond::If) { 82ed8f7882SAaron Ballman // #if or similar, starting a nested conditional block. 83ed8f7882SAaron Ballman DirectiveTree::Conditional Conditional; 84ed8f7882SAaron Ballman Conditional.Branches.emplace_back(); 85ed8f7882SAaron Ballman Conditional.Branches.back().first = std::move(Directive); 86ed8f7882SAaron Ballman parseConditional(&Conditional); 87ed8f7882SAaron Ballman Tree->Chunks.push_back(std::move(Conditional)); 88ed8f7882SAaron Ballman } else if ((Kind == Cond::Else || Kind == Cond::End) && !TopLevel) { 89ed8f7882SAaron Ballman // #endif or similar, ending this PStructure scope. 90ed8f7882SAaron Ballman // (#endif is unexpected at the top level, treat as simple directive). 91ed8f7882SAaron Ballman return std::move(Directive); 92ed8f7882SAaron Ballman } else { 93ed8f7882SAaron Ballman // #define or similar, a simple directive at the current scope. 94ed8f7882SAaron Ballman Tree->Chunks.push_back(std::move(Directive)); 95ed8f7882SAaron Ballman } 96ed8f7882SAaron Ballman } 97ed8f7882SAaron Ballman return std::nullopt; 98ed8f7882SAaron Ballman } 99ed8f7882SAaron Ballman 100ed8f7882SAaron Ballman // Parse the rest of a conditional section, after seeing the If directive. 101ed8f7882SAaron Ballman // Returns after consuming the End directive. 102ed8f7882SAaron Ballman void parseConditional(DirectiveTree::Conditional *C) { 103ed8f7882SAaron Ballman assert(C->Branches.size() == 1 && 104ed8f7882SAaron Ballman C->Branches.front().second.Chunks.empty() && 105ed8f7882SAaron Ballman "Should be ready to parse first branch body"); 106ed8f7882SAaron Ballman while (Tok->Kind != tok::eof) { 107ed8f7882SAaron Ballman auto Terminator = parse(&C->Branches.back().second, /*TopLevel=*/false); 108ed8f7882SAaron Ballman if (!Terminator) { 109ed8f7882SAaron Ballman assert(Tok->Kind == tok::eof && "gave up parsing before eof?"); 110ed8f7882SAaron Ballman C->End.Tokens = Token::Range::emptyAt(Code.index(*Tok)); 111ed8f7882SAaron Ballman return; 112ed8f7882SAaron Ballman } 113ed8f7882SAaron Ballman if (classifyDirective(Terminator->Kind) == Cond::End) { 114ed8f7882SAaron Ballman C->End = std::move(*Terminator); 115ed8f7882SAaron Ballman return; 116ed8f7882SAaron Ballman } 117ed8f7882SAaron Ballman assert(classifyDirective(Terminator->Kind) == Cond::Else && 118ed8f7882SAaron Ballman "ended branch unexpectedly"); 119ed8f7882SAaron Ballman C->Branches.emplace_back(); 120ed8f7882SAaron Ballman C->Branches.back().first = std::move(*Terminator); 121ed8f7882SAaron Ballman } 122ed8f7882SAaron Ballman } 123ed8f7882SAaron Ballman 124ed8f7882SAaron Ballman // Parse a directive. Tok is the hash. 125ed8f7882SAaron Ballman void parseDirective(DirectiveTree::Directive *D) { 126ed8f7882SAaron Ballman assert(Tok->Kind == tok::hash); 127ed8f7882SAaron Ballman 128ed8f7882SAaron Ballman // Directive spans from the hash until the end of line or file. 129ed8f7882SAaron Ballman const Token *Begin = Tok++; 130ed8f7882SAaron Ballman while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine)) 131ed8f7882SAaron Ballman ++Tok; 132ed8f7882SAaron Ballman ArrayRef<Token> Tokens{Begin, Tok}; 133ed8f7882SAaron Ballman D->Tokens = {Code.index(*Tokens.begin()), Code.index(*Tokens.end())}; 134ed8f7882SAaron Ballman 135ed8f7882SAaron Ballman // Directive name is the first non-comment token after the hash. 136ed8f7882SAaron Ballman Tokens = Tokens.drop_front().drop_while( 137ed8f7882SAaron Ballman [](const Token &T) { return T.Kind == tok::comment; }); 138ed8f7882SAaron Ballman if (!Tokens.empty()) 139ed8f7882SAaron Ballman D->Kind = PPKeywords.get(Tokens.front().text()).getPPKeywordID(); 140ed8f7882SAaron Ballman } 141ed8f7882SAaron Ballman 142ed8f7882SAaron Ballman const TokenStream &Code; 143ed8f7882SAaron Ballman const Token *Tok; 144ed8f7882SAaron Ballman clang::IdentifierTable PPKeywords; 145ed8f7882SAaron Ballman }; 146ed8f7882SAaron Ballman 147ed8f7882SAaron Ballman struct Dumper { 148ed8f7882SAaron Ballman llvm::raw_ostream &OS; 149ed8f7882SAaron Ballman unsigned Indent = 0; 150ed8f7882SAaron Ballman 151ed8f7882SAaron Ballman Dumper(llvm::raw_ostream& OS) : OS(OS) {} 152ed8f7882SAaron Ballman void operator()(const DirectiveTree& Tree) { 153ed8f7882SAaron Ballman for (const auto& Chunk : Tree.Chunks) 154ed8f7882SAaron Ballman std::visit(*this, Chunk); 155ed8f7882SAaron Ballman } 156ed8f7882SAaron Ballman void operator()(const DirectiveTree::Conditional &Conditional) { 157ed8f7882SAaron Ballman for (unsigned I = 0; I < Conditional.Branches.size(); ++I) { 158ed8f7882SAaron Ballman const auto &Branch = Conditional.Branches[I]; 159ed8f7882SAaron Ballman (*this)(Branch.first, Conditional.Taken == I); 160ed8f7882SAaron Ballman Indent += 2; 161ed8f7882SAaron Ballman (*this)(Branch.second); 162ed8f7882SAaron Ballman Indent -= 2; 163ed8f7882SAaron Ballman } 164ed8f7882SAaron Ballman (*this)(Conditional.End); 165ed8f7882SAaron Ballman } 166ed8f7882SAaron Ballman void operator()(const DirectiveTree::Directive &Directive, 167ed8f7882SAaron Ballman bool Taken = false) { 168ed8f7882SAaron Ballman OS.indent(Indent) << llvm::formatv( 169ed8f7882SAaron Ballman "#{0} ({1} tokens){2}\n", tok::getPPKeywordSpelling(Directive.Kind), 170ed8f7882SAaron Ballman Directive.Tokens.size(), Taken ? " TAKEN" : ""); 171ed8f7882SAaron Ballman } 172ed8f7882SAaron Ballman void operator()(const DirectiveTree::Code &Code) { 173ed8f7882SAaron Ballman OS.indent(Indent) << llvm::formatv("code ({0} tokens)\n", 174ed8f7882SAaron Ballman Code.Tokens.size()); 175ed8f7882SAaron Ballman } 176ed8f7882SAaron Ballman }; 177ed8f7882SAaron Ballman } // namespace 178ed8f7882SAaron Ballman 179ed8f7882SAaron Ballman DirectiveTree DirectiveTree::parse(const TokenStream &Code) { 180ed8f7882SAaron Ballman DirectiveTree Result; 181ed8f7882SAaron Ballman DirectiveParser(Code).parse(&Result); 182ed8f7882SAaron Ballman return Result; 183ed8f7882SAaron Ballman } 184ed8f7882SAaron Ballman 185ed8f7882SAaron Ballman // Define operator<< in terms of dump() functions above. 186ed8f7882SAaron Ballman #define OSTREAM_DUMP(Type) \ 187ed8f7882SAaron Ballman llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Type &T) { \ 188ed8f7882SAaron Ballman Dumper{OS}(T); \ 189ed8f7882SAaron Ballman return OS; \ 190ed8f7882SAaron Ballman } 191ed8f7882SAaron Ballman OSTREAM_DUMP(DirectiveTree) 192ed8f7882SAaron Ballman OSTREAM_DUMP(DirectiveTree::Directive) 193ed8f7882SAaron Ballman OSTREAM_DUMP(DirectiveTree::Conditional) 194ed8f7882SAaron Ballman OSTREAM_DUMP(DirectiveTree::Code) 195ed8f7882SAaron Ballman #undef OSTREAM_DUMP 196ed8f7882SAaron Ballman 197ed8f7882SAaron Ballman namespace { 198ed8f7882SAaron Ballman // Makes choices about conditional branches. 199ed8f7882SAaron Ballman // 200ed8f7882SAaron Ballman // Generally it tries to maximize the amount of useful code we see. 201ed8f7882SAaron Ballman // 202ed8f7882SAaron Ballman // Caveat: each conditional is evaluated independently. Consider this code: 203ed8f7882SAaron Ballman // #ifdef WINDOWS 204ed8f7882SAaron Ballman // bool isWindows = true; 205ed8f7882SAaron Ballman // #endif 206ed8f7882SAaron Ballman // #ifndef WINDOWS 207ed8f7882SAaron Ballman // bool isWindows = false; 208ed8f7882SAaron Ballman // #endif 209ed8f7882SAaron Ballman // We take both branches and define isWindows twice. We could track more state 210ed8f7882SAaron Ballman // in order to produce a consistent view, but this is complex. 211ed8f7882SAaron Ballman class BranchChooser { 212ed8f7882SAaron Ballman public: 213ed8f7882SAaron Ballman BranchChooser(const TokenStream &Code) : Code(Code) {} 214ed8f7882SAaron Ballman 215ed8f7882SAaron Ballman // Describes code seen by making particular branch choices. Higher is better. 216ed8f7882SAaron Ballman struct Score { 217ed8f7882SAaron Ballman int Tokens = 0; // excluding comments and directives 218ed8f7882SAaron Ballman int Directives = 0; 219ed8f7882SAaron Ballman int Errors = 0; // #error directives 220ed8f7882SAaron Ballman 221ed8f7882SAaron Ballman bool operator>(const Score &Other) const { 222ed8f7882SAaron Ballman // Seeing errors is bad, other things are good. 223ed8f7882SAaron Ballman return std::make_tuple(-Errors, Tokens, Directives) > 224ed8f7882SAaron Ballman std::make_tuple(-Other.Errors, Other.Tokens, Other.Directives); 225ed8f7882SAaron Ballman } 226ed8f7882SAaron Ballman 227ed8f7882SAaron Ballman Score &operator+=(const Score &Other) { 228ed8f7882SAaron Ballman Tokens += Other.Tokens; 229ed8f7882SAaron Ballman Directives += Other.Directives; 230ed8f7882SAaron Ballman Errors += Other.Errors; 231ed8f7882SAaron Ballman return *this; 232ed8f7882SAaron Ballman } 233ed8f7882SAaron Ballman }; 234ed8f7882SAaron Ballman 235ed8f7882SAaron Ballman Score operator()(DirectiveTree::Code &C) { 236ed8f7882SAaron Ballman Score S; 237ed8f7882SAaron Ballman for (const Token &T : Code.tokens(C.Tokens)) 238ed8f7882SAaron Ballman if (T.Kind != tok::comment) 239ed8f7882SAaron Ballman ++S.Tokens; 240ed8f7882SAaron Ballman return S; 241ed8f7882SAaron Ballman } 242ed8f7882SAaron Ballman 243ed8f7882SAaron Ballman Score operator()(DirectiveTree::Directive &D) { 244ed8f7882SAaron Ballman Score S; 245ed8f7882SAaron Ballman S.Directives = 1; 246ed8f7882SAaron Ballman S.Errors = D.Kind == tok::pp_error; 247ed8f7882SAaron Ballman return S; 248ed8f7882SAaron Ballman } 249ed8f7882SAaron Ballman 250ed8f7882SAaron Ballman Score operator()(DirectiveTree::Conditional &C) { 251ed8f7882SAaron Ballman Score Best; 252ed8f7882SAaron Ballman bool MayTakeTrivial = true; 253ed8f7882SAaron Ballman bool TookTrivial = false; 254ed8f7882SAaron Ballman 255ed8f7882SAaron Ballman for (unsigned I = 0; I < C.Branches.size(); ++I) { 256ed8f7882SAaron Ballman // Walk the branch to make its nested choices in any case. 257ed8f7882SAaron Ballman Score BranchScore = walk(C.Branches[I].second); 258ed8f7882SAaron Ballman // If we already took an #if 1, don't consider any other branches. 259ed8f7882SAaron Ballman if (TookTrivial) 260ed8f7882SAaron Ballman continue; 261ed8f7882SAaron Ballman // Is this a trivial #if 0 or #if 1? 262ed8f7882SAaron Ballman if (auto TriviallyTaken = isTakenWhenReached(C.Branches[I].first)) { 263ed8f7882SAaron Ballman if (!*TriviallyTaken) 264ed8f7882SAaron Ballman continue; // Don't consider #if 0 even if it scores well. 265ed8f7882SAaron Ballman if (MayTakeTrivial) 266ed8f7882SAaron Ballman TookTrivial = true; 267ed8f7882SAaron Ballman } else { 268ed8f7882SAaron Ballman // After a nontrivial condition, #elif 1 isn't guaranteed taken. 269ed8f7882SAaron Ballman MayTakeTrivial = false; 270ed8f7882SAaron Ballman } 271ed8f7882SAaron Ballman // Is this the best branch so far? (Including if it's #if 1). 272ed8f7882SAaron Ballman if (TookTrivial || !C.Taken || BranchScore > Best) { 273ed8f7882SAaron Ballman Best = BranchScore; 274ed8f7882SAaron Ballman C.Taken = I; 275ed8f7882SAaron Ballman } 276ed8f7882SAaron Ballman } 277ed8f7882SAaron Ballman return Best; 278ed8f7882SAaron Ballman } 279ed8f7882SAaron Ballman Score walk(DirectiveTree &M) { 280ed8f7882SAaron Ballman Score S; 281ed8f7882SAaron Ballman for (auto &C : M.Chunks) 282ed8f7882SAaron Ballman S += std::visit(*this, C); 283ed8f7882SAaron Ballman return S; 284ed8f7882SAaron Ballman } 285ed8f7882SAaron Ballman 286ed8f7882SAaron Ballman private: 287ed8f7882SAaron Ballman // Return true if the directive starts an always-taken conditional branch, 288ed8f7882SAaron Ballman // false if the branch is never taken, and std::nullopt otherwise. 289ed8f7882SAaron Ballman std::optional<bool> isTakenWhenReached(const DirectiveTree::Directive &Dir) { 290ed8f7882SAaron Ballman switch (Dir.Kind) { 291ed8f7882SAaron Ballman case clang::tok::pp_if: 292ed8f7882SAaron Ballman case clang::tok::pp_elif: 293ed8f7882SAaron Ballman break; // handled below 294ed8f7882SAaron Ballman case clang::tok::pp_else: 295ed8f7882SAaron Ballman return true; 296ed8f7882SAaron Ballman default: // #ifdef etc 297ed8f7882SAaron Ballman return std::nullopt; 298ed8f7882SAaron Ballman } 299ed8f7882SAaron Ballman 300ed8f7882SAaron Ballman const auto &Tokens = Code.tokens(Dir.Tokens); 301ed8f7882SAaron Ballman assert(!Tokens.empty() && Tokens.front().Kind == tok::hash); 302ed8f7882SAaron Ballman const Token &Name = Tokens.front().nextNC(); 303ed8f7882SAaron Ballman const Token &Value = Name.nextNC(); 304ed8f7882SAaron Ballman // Does the condition consist of exactly one token? 305ed8f7882SAaron Ballman if (&Value >= Tokens.end() || &Value.nextNC() < Tokens.end()) 306ed8f7882SAaron Ballman return std::nullopt; 307ed8f7882SAaron Ballman return llvm::StringSwitch<std::optional<bool>>(Value.text()) 308ed8f7882SAaron Ballman .Cases("true", "1", true) 309ed8f7882SAaron Ballman .Cases("false", "0", false) 310ed8f7882SAaron Ballman .Default(std::nullopt); 311ed8f7882SAaron Ballman } 312ed8f7882SAaron Ballman 313ed8f7882SAaron Ballman const TokenStream &Code; 314ed8f7882SAaron Ballman }; 315ed8f7882SAaron Ballman 316ed8f7882SAaron Ballman } // namespace 317ed8f7882SAaron Ballman 318ed8f7882SAaron Ballman void chooseConditionalBranches(DirectiveTree &Tree, const TokenStream &Code) { 319ed8f7882SAaron Ballman BranchChooser{Code}.walk(Tree); 320ed8f7882SAaron Ballman } 321ed8f7882SAaron Ballman 322ed8f7882SAaron Ballman namespace { 323ed8f7882SAaron Ballman class Preprocessor { 324ed8f7882SAaron Ballman const TokenStream &In; 325ed8f7882SAaron Ballman TokenStream &Out; 326ed8f7882SAaron Ballman 327ed8f7882SAaron Ballman public: 328ed8f7882SAaron Ballman Preprocessor(const TokenStream &In, TokenStream &Out) : In(In), Out(Out) {} 329ed8f7882SAaron Ballman ~Preprocessor() { Out.finalize(); } 330ed8f7882SAaron Ballman 331*f59b600cSZahira Ammarguellat Preprocessor(const Preprocessor &other) = delete; 332*f59b600cSZahira Ammarguellat Preprocessor &operator=(const Preprocessor &other) = delete; 333*f59b600cSZahira Ammarguellat 334ed8f7882SAaron Ballman void walk(const DirectiveTree &T) { 335ed8f7882SAaron Ballman for (const auto &C : T.Chunks) 336ed8f7882SAaron Ballman std::visit(*this, C); 337ed8f7882SAaron Ballman } 338ed8f7882SAaron Ballman 339ed8f7882SAaron Ballman void operator()(const DirectiveTree::Code &C) { 340ed8f7882SAaron Ballman for (const auto &Tok : In.tokens(C.Tokens)) 341ed8f7882SAaron Ballman Out.push(Tok); 342ed8f7882SAaron Ballman } 343ed8f7882SAaron Ballman 344ed8f7882SAaron Ballman void operator()(const DirectiveTree::Directive &) {} 345ed8f7882SAaron Ballman 346ed8f7882SAaron Ballman void operator()(const DirectiveTree::Conditional &C) { 347ed8f7882SAaron Ballman if (C.Taken) 348ed8f7882SAaron Ballman walk(C.Branches[*C.Taken].second); 349ed8f7882SAaron Ballman } 350ed8f7882SAaron Ballman }; 351ed8f7882SAaron Ballman } // namespace 352ed8f7882SAaron Ballman 353ed8f7882SAaron Ballman TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const { 354ed8f7882SAaron Ballman TokenStream Out; 355ed8f7882SAaron Ballman Preprocessor(In, Out).walk(*this); 356ed8f7882SAaron Ballman return Out; 357ed8f7882SAaron Ballman } 358ed8f7882SAaron Ballman 359ed8f7882SAaron Ballman } // namespace clangd 360ed8f7882SAaron Ballman } // namespace clang 361