1453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor//===--- LayoutOverrideSource.cpp --Override Record Layouts ---------------===// 2453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 3453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// The LLVM Compiler Infrastructure 4453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 5453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// This file is distributed under the University of Illinois Open Source 6453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// License. See LICENSE.TXT for details. 7453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor// 8453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor//===----------------------------------------------------------------------===// 9453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "clang/Frontend/LayoutOverrideSource.h" 10453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "clang/AST/Decl.h" 113f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose#include "clang/Basic/CharInfo.h" 12453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include "llvm/Support/raw_ostream.h" 13453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include <fstream> 14453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor#include <string> 15453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 16453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorusing namespace clang; 17453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 18453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor/// \brief Parse a simple identifier. 195bbc385ad2d8e487edfbc2756eaf4fb0b920cfe4Benjamin Kramerstatic std::string parseName(StringRef S) { 203f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose if (S.empty() || !isIdentifierHead(S[0])) 213f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose return ""; 223f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose 233f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose unsigned Offset = 1; 243f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose while (Offset < S.size() && isIdentifierBody(S[Offset])) 25453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ++Offset; 26453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 27453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return S.substr(0, Offset).str(); 28453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 29453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 30cfa88f893915ceb8ae4ce2f17c46c24a4d67502fDmitri GribenkoLayoutOverrideSource::LayoutOverrideSource(StringRef Filename) { 31453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::ifstream Input(Filename.str().c_str()); 32453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!Input.is_open()) 33453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return; 34453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 35453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse the output of -fdump-record-layouts. 36453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::string CurrentType; 37453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layout CurrentLayout; 38453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor bool ExpectingType = false; 39453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 40453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor while (Input.good()) { 41453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor std::string Line; 42453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor getline(Input, Line); 43453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 44453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor StringRef LineStr(Line); 45453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 46453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Determine whether the following line will start a 47453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (LineStr.find("*** Dumping AST Record Layout") != StringRef::npos) { 48453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Flush the last type/layout, if there is one. 49453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!CurrentType.empty()) 50453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layouts[CurrentType] = CurrentLayout; 51453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout = Layout(); 52453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 53453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ExpectingType = true; 54453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 55453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 56453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 57453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // If we're expecting a type, grab it. 58453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (ExpectingType) { 59453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ExpectingType = false; 60453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 61453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor StringRef::size_type Pos; 62453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if ((Pos = LineStr.find("struct ")) != StringRef::npos) 63453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("struct ")); 64453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else if ((Pos = LineStr.find("class ")) != StringRef::npos) 65453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("class ")); 66453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else if ((Pos = LineStr.find("union ")) != StringRef::npos) 67453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("union ")); 68453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor else 69453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 70453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 71453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Find the name of the type. 72453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentType = parseName(LineStr); 73453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout = Layout(); 74453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 75453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 76453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 77453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the size of the type. 78285c6070cba54ab9bb1d3bacdc2028498a83baefDouglas Gregor StringRef::size_type Pos = LineStr.find(" Size:"); 79453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 80285c6070cba54ab9bb1d3bacdc2028498a83baefDouglas Gregor // Skip past the " Size:" prefix. 81285c6070cba54ab9bb1d3bacdc2028498a83baefDouglas Gregor LineStr = LineStr.substr(Pos + strlen(" Size:")); 82453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 83453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Size = 0; 84453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Size); 85453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Size = Size; 86453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 87453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 88453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 89453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the alignment of the type. 90453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("Alignment:"); 91453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 92453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Skip past the "Alignment:" prefix. 93453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("Alignment:")); 94453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 95453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Alignment = 0; 96453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Alignment); 97453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Align = Alignment; 98453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 99453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 100453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 101453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the size/alignment of the type. 102453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("sizeof="); 103453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 104453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor /* Skip past the sizeof= prefix. */ 105453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("sizeof=")); 106453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 107453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse size. 108453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Size = 0; 109453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Size); 110453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Size = Size; 111453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 112453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("align="); 113453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos != StringRef::npos) { 114453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor /* Skip past the align= prefix. */ 115453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("align=")); 116453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 117453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse alignment. 118453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Alignment = 0; 119453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.getAsInteger(10, Alignment); 120453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.Align = Alignment; 121453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 122453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 123453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 124453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 125453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 126453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check for the field offsets of the type. 127453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Pos = LineStr.find("FieldOffsets: ["); 128453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Pos == StringRef::npos) 129453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 130453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 131453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Pos + strlen("FieldOffsets: [")); 1323f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose while (!LineStr.empty() && isDigit(LineStr[0])) { 133453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Parse this offset. 134453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned Idx = 1; 1353f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose while (Idx < LineStr.size() && isDigit(LineStr[Idx])) 136453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor ++Idx; 137453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 138453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned long long Offset = 0; 139453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor (void)LineStr.substr(0, Idx).getAsInteger(10, Offset); 140453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 141453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor CurrentLayout.FieldOffsets.push_back(Offset); 142453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 143453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Skip over this offset, the following comma, and any spaces. 144453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(Idx + 1); 1453f6f51e28231f65de9c2dd150a2d757b2162cfa3Jordan Rose while (!LineStr.empty() && isWhitespace(LineStr[0])) 146453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LineStr = LineStr.substr(1); 147453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 148453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 149453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 150453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Flush the last type/layout, if there is one. 151453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!CurrentType.empty()) 152453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Layouts[CurrentType] = CurrentLayout; 153453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 154453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 155453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorbool 156453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas GregorLayoutOverrideSource::layoutRecordType(const RecordDecl *Record, 157453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor uint64_t &Size, uint64_t &Alignment, 158453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const FieldDecl *, uint64_t> &FieldOffsets, 159453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const CXXRecordDecl *, CharUnits> &BaseOffsets, 160453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::DenseMap<const CXXRecordDecl *, CharUnits> &VirtualBaseOffsets) 161453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor{ 162453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // We can't override unnamed declarations. 163453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (!Record->getIdentifier()) 164453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 165453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 166453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Check whether we have a layout for this record. 167453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor llvm::StringMap<Layout>::iterator Known = Layouts.find(Record->getName()); 168453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (Known == Layouts.end()) 169453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 170453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 171453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Provide field layouts. 172453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor unsigned NumFields = 0; 173453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (RecordDecl::field_iterator F = Record->field_begin(), 174453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor FEnd = Record->field_end(); 175453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor F != FEnd; ++F, ++NumFields) { 176453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (NumFields >= Known->second.FieldOffsets.size()) 177453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor continue; 178453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 179581deb3da481053c4993c7600f97acf7768caac5David Blaikie FieldOffsets[*F] = Known->second.FieldOffsets[NumFields]; 180453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 181453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 182453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor // Wrong number of fields. 183453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (NumFields != Known->second.FieldOffsets.size()) 184453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return false; 185453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 186453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Size = Known->second.Size; 187453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor Alignment = Known->second.Align; 188453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor return true; 189453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 190453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 191453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregorvoid LayoutOverrideSource::dump() { 192cfa88f893915ceb8ae4ce2f17c46c24a4d67502fDmitri Gribenko raw_ostream &OS = llvm::errs(); 193453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (llvm::StringMap<Layout>::iterator L = Layouts.begin(), 194453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor LEnd = Layouts.end(); 195453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor L != LEnd; ++L) { 196453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << "Type: blah " << L->first() << '\n'; 197453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " Size:" << L->second.Size << '\n'; 198453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " Alignment:" << L->second.Align << '\n'; 199453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << " FieldOffsets: ["; 200453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor for (unsigned I = 0, N = L->second.FieldOffsets.size(); I != N; ++I) { 201453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor if (I) 202453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << ", "; 203453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << L->second.FieldOffsets[I]; 204453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 205453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor OS << "]\n"; 206453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor } 207453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor} 208453dbcbe30093fbf947a0bec2fbd46e9694eafe9Douglas Gregor 209