538 lines · cpp
1//===- llvm/unittest/AsmParser/AsmParserTest.cpp - asm parser unittests ---===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/ADT/STLExtras.h"10#include "llvm/ADT/StringRef.h"11#include "llvm/AsmParser/AsmParserContext.h"12#include "llvm/AsmParser/Parser.h"13#include "llvm/AsmParser/SlotMapping.h"14#include "llvm/IR/Constants.h"15#include "llvm/IR/DataLayout.h"16#include "llvm/IR/DebugInfoMetadata.h"17#include "llvm/IR/LLVMContext.h"18#include "llvm/IR/Module.h"19#include "llvm/IR/Value.h"20#include "llvm/Support/Debug.h"21#include "llvm/Support/Error.h"22#include "llvm/Support/SourceMgr.h"23#include "gtest/gtest.h"24 25#define DEBUG_TYPE "unittest-asm-parser-tests"26 27using namespace llvm;28 29namespace {30 31TEST(AsmParserTest, NullTerminatedInput) {32 LLVMContext Ctx;33 StringRef Source = "; Empty module \n";34 SMDiagnostic Error;35 auto Mod = parseAssemblyString(Source, Error, Ctx);36 37 EXPECT_TRUE(Mod != nullptr);38 EXPECT_TRUE(Error.getMessage().empty());39}40 41#ifdef GTEST_HAS_DEATH_TEST42#ifndef NDEBUG43 44TEST(AsmParserTest, NonNullTerminatedInput) {45 LLVMContext Ctx;46 StringRef Source = "; Empty module \n\1\2";47 SMDiagnostic Error;48 std::unique_ptr<Module> Mod;49 EXPECT_DEATH(Mod = parseAssemblyString(Source.substr(0, Source.size() - 2),50 Error, Ctx),51 "Buffer is not null terminated!");52}53 54#endif55#endif56 57TEST(AsmParserTest, SlotMappingTest) {58 LLVMContext Ctx;59 StringRef Source = "@0 = global i32 0\n !0 = !{}\n !42 = !{i32 42}";60 SMDiagnostic Error;61 SlotMapping Mapping;62 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping);63 64 EXPECT_TRUE(Mod != nullptr);65 EXPECT_TRUE(Error.getMessage().empty());66 67 ASSERT_EQ(Mapping.GlobalValues.getNext(), 1u);68 EXPECT_TRUE(isa<GlobalVariable>(Mapping.GlobalValues.get(0)));69 70 EXPECT_EQ(Mapping.MetadataNodes.size(), 2u);71 EXPECT_EQ(Mapping.MetadataNodes.count(0), 1u);72 EXPECT_EQ(Mapping.MetadataNodes.count(42), 1u);73 EXPECT_EQ(Mapping.MetadataNodes.count(1), 0u);74}75 76TEST(AsmParserTest, TypeAndConstantValueParsing) {77 LLVMContext Ctx;78 SMDiagnostic Error;79 StringRef Source = "define void @test() {\n entry:\n ret void\n}";80 auto Mod = parseAssemblyString(Source, Error, Ctx);81 ASSERT_TRUE(Mod != nullptr);82 auto &M = *Mod;83 84 const Value *V;85 V = parseConstantValue("double 3.5", Error, M);86 ASSERT_TRUE(V);87 EXPECT_TRUE(V->getType()->isDoubleTy());88 ASSERT_TRUE(isa<ConstantFP>(V));89 EXPECT_TRUE(cast<ConstantFP>(V)->isExactlyValue(3.5));90 91 V = parseConstantValue("i32 42", Error, M);92 ASSERT_TRUE(V);93 EXPECT_TRUE(V->getType()->isIntegerTy());94 ASSERT_TRUE(isa<ConstantInt>(V));95 EXPECT_TRUE(cast<ConstantInt>(V)->equalsInt(42));96 97 V = parseConstantValue("<4 x i32> <i32 0, i32 1, i32 2, i32 3>", Error, M);98 ASSERT_TRUE(V);99 EXPECT_TRUE(V->getType()->isVectorTy());100 ASSERT_TRUE(isa<ConstantDataVector>(V));101 102 V = parseConstantValue("<4 x i32> splat (i32 -2)", Error, M);103 ASSERT_TRUE(V);104 EXPECT_TRUE(V->getType()->isVectorTy());105 ASSERT_TRUE(isa<ConstantDataVector>(V));106 107 V = parseConstantValue("<4 x i32> zeroinitializer", Error, M);108 ASSERT_TRUE(V);109 EXPECT_TRUE(V->getType()->isVectorTy());110 ASSERT_TRUE(isa<Constant>(V));111 EXPECT_TRUE(cast<Constant>(V)->isNullValue());112 113 V = parseConstantValue("<4 x i32> poison", Error, M);114 ASSERT_TRUE(V);115 EXPECT_TRUE(V->getType()->isVectorTy());116 ASSERT_TRUE(isa<PoisonValue>(V));117 118 V = parseConstantValue("i32 add (i32 1, i32 2)", Error, M);119 ASSERT_TRUE(V);120 ASSERT_TRUE(isa<ConstantInt>(V));121 122 V = parseConstantValue("ptr blockaddress(@test, %entry)", Error, M);123 ASSERT_TRUE(V);124 ASSERT_TRUE(isa<BlockAddress>(V));125 126 V = parseConstantValue("ptr undef", Error, M);127 ASSERT_TRUE(V);128 ASSERT_TRUE(isa<UndefValue>(V));129 130 V = parseConstantValue("ptr poison", Error, M);131 ASSERT_TRUE(V);132 ASSERT_TRUE(isa<PoisonValue>(V));133 134 EXPECT_FALSE(parseConstantValue("duble 3.25", Error, M));135 EXPECT_EQ(Error.getMessage(), "expected type");136 137 EXPECT_FALSE(parseConstantValue("i32 3.25", Error, M));138 EXPECT_EQ(Error.getMessage(), "floating point constant invalid for type");139 140 EXPECT_FALSE(parseConstantValue("ptr @foo", Error, M));141 EXPECT_EQ(Error.getMessage(), "expected a constant value");142 143 EXPECT_FALSE(parseConstantValue("i32 3, ", Error, M));144 EXPECT_EQ(Error.getMessage(), "expected end of string");145}146 147TEST(AsmParserTest, TypeAndConstantValueWithSlotMappingParsing) {148 LLVMContext Ctx;149 SMDiagnostic Error;150 StringRef Source =151 "%st = type { i32, i32 }\n"152 "@v = common global [50 x %st] zeroinitializer, align 16\n"153 "%0 = type { i32, i32, i32, i32 }\n"154 "@g = common global [50 x %0] zeroinitializer, align 16\n"155 "define void @marker4(i64 %d) {\n"156 "entry:\n"157 " %conv = trunc i64 %d to i32\n"158 " store i32 %conv, ptr getelementptr inbounds "159 " ([50 x %st], ptr @v, i64 0, i64 1, i32 0), align 16\n"160 " store i32 %conv, ptr getelementptr inbounds "161 " ([50 x %0], ptr @g, i64 0, i64 1, i32 0), align 16\n"162 " ret void\n"163 "}";164 SlotMapping Mapping;165 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping);166 ASSERT_TRUE(Mod != nullptr);167 auto &M = *Mod;168 169 const Value *V;170 V = parseConstantValue("ptr getelementptr inbounds ([50 x %st], ptr "171 "@v, i64 0, i64 1, i32 0)",172 Error, M, &Mapping);173 ASSERT_TRUE(V);174 ASSERT_TRUE(isa<ConstantExpr>(V));175 176 V = parseConstantValue("ptr getelementptr inbounds ([50 x %0], ptr "177 "@g, i64 0, i64 1, i32 0)",178 Error, M, &Mapping);179 ASSERT_TRUE(V);180 ASSERT_TRUE(isa<ConstantExpr>(V));181}182 183TEST(AsmParserTest, TypeWithSlotMappingParsing) {184 LLVMContext Ctx;185 SMDiagnostic Error;186 StringRef Source =187 "%st = type { i32, i32 }\n"188 "@v = common global [50 x %st] zeroinitializer, align 16\n"189 "%0 = type { i32, i32, i32, i32 }\n"190 "@g = common global [50 x %0] zeroinitializer, align 16\n"191 "define void @marker4(i64 %d) {\n"192 "entry:\n"193 " %conv = trunc i64 %d to i32\n"194 " store i32 %conv, ptr getelementptr inbounds "195 " ([50 x %st], ptr @v, i64 0, i64 0, i32 0), align 16\n"196 " store i32 %conv, ptr getelementptr inbounds "197 " ([50 x %0], ptr @g, i64 0, i64 0, i32 0), align 16\n"198 " ret void\n"199 "}";200 SlotMapping Mapping;201 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping);202 ASSERT_TRUE(Mod != nullptr);203 auto &M = *Mod;204 205 // Check we properly parse integer types.206 Type *Ty;207 Ty = parseType("i32", Error, M, &Mapping);208 ASSERT_TRUE(Ty);209 ASSERT_TRUE(Ty->isIntegerTy());210 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);211 212 // Check we properly parse integer types with exotic size.213 Ty = parseType("i13", Error, M, &Mapping);214 ASSERT_TRUE(Ty);215 ASSERT_TRUE(Ty->isIntegerTy());216 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 13);217 218 // Check we properly parse floating point types.219 Ty = parseType("float", Error, M, &Mapping);220 ASSERT_TRUE(Ty);221 ASSERT_TRUE(Ty->isFloatTy());222 223 Ty = parseType("double", Error, M, &Mapping);224 ASSERT_TRUE(Ty);225 ASSERT_TRUE(Ty->isDoubleTy());226 227 // Check we properly parse struct types.228 // Named struct.229 Ty = parseType("%st", Error, M, &Mapping);230 ASSERT_TRUE(Ty);231 ASSERT_TRUE(Ty->isStructTy());232 233 // Check the details of the struct.234 StructType *ST = cast<StructType>(Ty);235 ASSERT_TRUE(ST->getNumElements() == 2);236 for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) {237 Ty = ST->getElementType(i);238 ASSERT_TRUE(Ty->isIntegerTy());239 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);240 }241 242 // Anonymous struct.243 Ty = parseType("%0", Error, M, &Mapping);244 ASSERT_TRUE(Ty);245 ASSERT_TRUE(Ty->isStructTy());246 247 // Check the details of the struct.248 ST = cast<StructType>(Ty);249 ASSERT_TRUE(ST->getNumElements() == 4);250 for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) {251 Ty = ST->getElementType(i);252 ASSERT_TRUE(Ty->isIntegerTy());253 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);254 }255 256 // Check we properly parse vector types.257 Ty = parseType("<5 x i32>", Error, M, &Mapping);258 ASSERT_TRUE(Ty);259 ASSERT_TRUE(Ty->isVectorTy());260 261 // Check the details of the vector.262 auto *VT = cast<FixedVectorType>(Ty);263 ASSERT_TRUE(VT->getNumElements() == 5);264 ASSERT_TRUE(VT->getPrimitiveSizeInBits().getFixedValue() == 160);265 Ty = VT->getElementType();266 ASSERT_TRUE(Ty->isIntegerTy());267 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);268 269 // Opaque struct.270 Ty = parseType("%opaque", Error, M, &Mapping);271 ASSERT_TRUE(Ty);272 ASSERT_TRUE(Ty->isStructTy());273 274 ST = cast<StructType>(Ty);275 ASSERT_TRUE(ST->isOpaque());276 277 // Check we properly parse pointer types.278 Ty = parseType("ptr", Error, M, &Mapping);279 ASSERT_TRUE(Ty);280 ASSERT_TRUE(Ty->isPointerTy());281 282 // Check that we reject types with garbage.283 Ty = parseType("i32 garbage", Error, M, &Mapping);284 ASSERT_TRUE(!Ty);285}286 287TEST(AsmParserTest, TypeAtBeginningWithSlotMappingParsing) {288 LLVMContext Ctx;289 SMDiagnostic Error;290 StringRef Source =291 "%st = type { i32, i32 }\n"292 "@v = common global [50 x %st] zeroinitializer, align 16\n"293 "%0 = type { i32, i32, i32, i32 }\n"294 "@g = common global [50 x %0] zeroinitializer, align 16\n"295 "define void @marker4(i64 %d) {\n"296 "entry:\n"297 " %conv = trunc i64 %d to i32\n"298 " store i32 %conv, ptr getelementptr inbounds "299 " ([50 x %st], ptr @v, i64 0, i64 0, i32 0), align 16\n"300 " store i32 %conv, ptr getelementptr inbounds "301 " ([50 x %0], ptr @g, i64 0, i64 0, i32 0), align 16\n"302 " ret void\n"303 "}";304 SlotMapping Mapping;305 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping);306 ASSERT_TRUE(Mod != nullptr);307 auto &M = *Mod;308 unsigned Read;309 310 // Check we properly parse integer types.311 Type *Ty;312 Ty = parseTypeAtBeginning("i32", Read, Error, M, &Mapping);313 ASSERT_TRUE(Ty);314 ASSERT_TRUE(Ty->isIntegerTy());315 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);316 ASSERT_TRUE(Read == 3);317 318 // Check we properly parse integer types with exotic size.319 Ty = parseTypeAtBeginning("i13", Read, Error, M, &Mapping);320 ASSERT_TRUE(Ty);321 ASSERT_TRUE(Ty->isIntegerTy());322 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 13);323 ASSERT_TRUE(Read == 3);324 325 // Check we properly parse floating point types.326 Ty = parseTypeAtBeginning("float", Read, Error, M, &Mapping);327 ASSERT_TRUE(Ty);328 ASSERT_TRUE(Ty->isFloatTy());329 ASSERT_TRUE(Read == 5);330 331 Ty = parseTypeAtBeginning("double", Read, Error, M, &Mapping);332 ASSERT_TRUE(Ty);333 ASSERT_TRUE(Ty->isDoubleTy());334 ASSERT_TRUE(Read == 6);335 336 // Check we properly parse struct types.337 // Named struct.338 Ty = parseTypeAtBeginning("%st", Read, Error, M, &Mapping);339 ASSERT_TRUE(Ty);340 ASSERT_TRUE(Ty->isStructTy());341 ASSERT_TRUE(Read == 3);342 343 // Check the details of the struct.344 StructType *ST = cast<StructType>(Ty);345 ASSERT_TRUE(ST->getNumElements() == 2);346 for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) {347 Ty = ST->getElementType(i);348 ASSERT_TRUE(Ty->isIntegerTy());349 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);350 }351 352 // Anonymous struct.353 Ty = parseTypeAtBeginning("%0", Read, Error, M, &Mapping);354 ASSERT_TRUE(Ty);355 ASSERT_TRUE(Ty->isStructTy());356 ASSERT_TRUE(Read == 2);357 358 // Check the details of the struct.359 ST = cast<StructType>(Ty);360 ASSERT_TRUE(ST->getNumElements() == 4);361 for (unsigned i = 0, e = ST->getNumElements(); i != e; ++i) {362 Ty = ST->getElementType(i);363 ASSERT_TRUE(Ty->isIntegerTy());364 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);365 }366 367 // Check we properly parse vector types.368 Ty = parseTypeAtBeginning("<5 x i32>", Read, Error, M, &Mapping);369 ASSERT_TRUE(Ty);370 ASSERT_TRUE(Ty->isVectorTy());371 ASSERT_TRUE(Read == 9);372 373 // Check the details of the vector.374 auto *VT = cast<FixedVectorType>(Ty);375 ASSERT_TRUE(VT->getNumElements() == 5);376 ASSERT_TRUE(VT->getPrimitiveSizeInBits().getFixedValue() == 160);377 Ty = VT->getElementType();378 ASSERT_TRUE(Ty->isIntegerTy());379 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);380 381 // Opaque struct.382 Ty = parseTypeAtBeginning("%opaque", Read, Error, M, &Mapping);383 ASSERT_TRUE(Ty);384 ASSERT_TRUE(Ty->isStructTy());385 ASSERT_TRUE(Read == 7);386 387 ST = cast<StructType>(Ty);388 ASSERT_TRUE(ST->isOpaque());389 390 // Check we properly parse pointer types.391 // One indirection.392 Ty = parseTypeAtBeginning("ptr", Read, Error, M, &Mapping);393 ASSERT_TRUE(Ty);394 ASSERT_TRUE(Ty->isPointerTy());395 ASSERT_TRUE(Read == 3);396 397 // Check that we reject types with garbage.398 Ty = parseTypeAtBeginning("i32 garbage", Read, Error, M, &Mapping);399 ASSERT_TRUE(Ty);400 ASSERT_TRUE(Ty->isIntegerTy());401 ASSERT_TRUE(Ty->getPrimitiveSizeInBits() == 32);402 // We go to the next token, i.e., we read "i32" + ' '.403 ASSERT_TRUE(Read == 4);404}405 406TEST(AsmParserTest, InvalidDataLayoutStringCallback) {407 LLVMContext Ctx;408 SMDiagnostic Error;409 // Note the invalid i8:7 part410 // Overalign i32 as marker so we can check that indeed this DL was used,411 // and not some default.412 StringRef InvalidDLStr =413 "e-m:e-p:64:64-i8:7-i16:16-i32:64-i64:64-f80:128-n8:16:32:64";414 StringRef FixedDLStr =415 "e-m:e-p:64:64-i8:8-i16:16-i32:64-i64:64-f80:128-n8:16:32:64";416 Expected<DataLayout> ExpectedFixedDL = DataLayout::parse(FixedDLStr);417 ASSERT_TRUE(!ExpectedFixedDL.takeError());418 DataLayout FixedDL = ExpectedFixedDL.get();419 std::string Source = ("target datalayout = \"" + InvalidDLStr + "\"\n").str();420 MemoryBufferRef SourceBuffer(Source, "<string>");421 422 // Check that we reject the source without a DL override.423 SlotMapping Mapping1;424 auto Mod1 = parseAssembly(SourceBuffer, Error, Ctx, &Mapping1);425 EXPECT_TRUE(Mod1 == nullptr);426 427 // Check that we pass the correct DL str to the callback,428 // that fixing the DL str from the callback works,429 // and that the resulting module has the correct DL.430 SlotMapping Mapping2;431 auto Mod2 = parseAssembly(432 SourceBuffer, Error, Ctx, &Mapping2,433 [&](StringRef Triple, StringRef DLStr) -> std::optional<std::string> {434 EXPECT_EQ(DLStr, InvalidDLStr);435 return std::string{FixedDLStr};436 });437 ASSERT_TRUE(Mod2 != nullptr);438 EXPECT_EQ(Mod2->getDataLayout(), FixedDL);439}440 441TEST(AsmParserTest, DIExpressionBodyAtBeginningWithSlotMappingParsing) {442 LLVMContext Ctx;443 SMDiagnostic Error;444 StringRef Source = "";445 SlotMapping Mapping;446 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping);447 ASSERT_TRUE(Mod != nullptr);448 auto &M = *Mod;449 unsigned Read;450 451 ASSERT_EQ(Mapping.MetadataNodes.size(), 0u);452 453 DIExpression *Expr;454 455 Expr = parseDIExpressionBodyAtBeginning("()", Read, Error, M, &Mapping);456 ASSERT_TRUE(Expr);457 ASSERT_EQ(Expr->getNumElements(), 0u);458 459 Expr = parseDIExpressionBodyAtBeginning("(0)", Read, Error, M, &Mapping);460 ASSERT_TRUE(Expr);461 ASSERT_EQ(Expr->getNumElements(), 1u);462 463 Expr = parseDIExpressionBodyAtBeginning("(DW_OP_LLVM_fragment, 0, 1)", Read,464 Error, M, &Mapping);465 ASSERT_TRUE(Expr);466 ASSERT_EQ(Expr->getNumElements(), 3u);467 468 Expr = parseDIExpressionBodyAtBeginning(469 "(DW_OP_LLVM_fragment, 0, 1) trailing source", Read, Error, M, &Mapping);470 ASSERT_TRUE(Expr);471 ASSERT_EQ(Expr->getNumElements(), 3u);472 ASSERT_EQ(Read, StringRef("(DW_OP_LLVM_fragment, 0, 1) ").size());473 474 Error = {};475 Expr = parseDIExpressionBodyAtBeginning("i32", Read, Error, M, &Mapping);476 ASSERT_FALSE(Expr);477 ASSERT_EQ(Error.getMessage(), "expected '(' here");478 479 Error = {};480 Expr = parseDIExpressionBodyAtBeginning(481 "!DIExpression(DW_OP_LLVM_fragment, 0, 1)", Read, Error, M, &Mapping);482 ASSERT_FALSE(Expr);483 ASSERT_EQ(Error.getMessage(), "expected '(' here");484 485 ASSERT_EQ(Mapping.MetadataNodes.size(), 0u);486}487 488#define ASSERT_EQ_LOC(Loc1, Loc2) \489 do { \490 EXPECT_TRUE(Loc1.contains(Loc2) && Loc2.contains(Loc1)) \491 << #Loc1 " location: " << Loc1.Start.Line << ":" << Loc1.Start.Col \492 << " - " << Loc1.End.Line << ":" << Loc1.End.Col << "\n" \493 << #Loc2 " location: " << Loc2.Start.Line << ":" << Loc2.Start.Col \494 << " - " << Loc2.End.Line << ":" << Loc2.End.Col << "\n"; \495 } while (false)496 497TEST(AsmParserTest, ParserObjectLocations) {498 StringRef Source = "define i32 @main() {\n"499 "entry:\n"500 " %a = add i32 1, 2\n"501 " ret i32 %a\n"502 "}\n";503 LLVMContext Ctx;504 SMDiagnostic Error;505 SlotMapping Mapping;506 AsmParserContext ParserContext;507 auto Mod = parseAssemblyString(Source, Error, Ctx, &Mapping, &ParserContext);508 509 auto *MainFn = Mod->getFunction("main");510 ASSERT_TRUE(MainFn != nullptr);511 512 auto MaybeMainLoc = ParserContext.getFunctionLocation(MainFn);513 EXPECT_TRUE(MaybeMainLoc.has_value());514 auto MainLoc = MaybeMainLoc.value();515 auto ExpectedMainLoc = FileLocRange(FileLoc{0, 0}, FileLoc{4, 1});516 ASSERT_EQ_LOC(MainLoc, ExpectedMainLoc);517 518 auto &EntryBB = MainFn->getEntryBlock();519 auto MaybeEntryBBLoc = ParserContext.getBlockLocation(&EntryBB);520 ASSERT_TRUE(MaybeEntryBBLoc.has_value());521 auto EntryBBLoc = MaybeEntryBBLoc.value();522 auto ExpectedEntryBBLoc = FileLocRange(FileLoc{1, 0}, FileLoc{3, 14});523 ASSERT_EQ_LOC(EntryBBLoc, ExpectedEntryBBLoc);524 525 SmallVector<FileLocRange> InstructionLocations = {526 FileLocRange(FileLoc{2, 4}, FileLoc{2, 21}),527 FileLocRange(FileLoc{3, 4}, FileLoc{3, 14})};528 529 for (const auto &[Inst, ExpectedLoc] : zip(EntryBB, InstructionLocations)) {530 auto MaybeInstLoc = ParserContext.getInstructionLocation(&Inst);531 ASSERT_TRUE(MaybeMainLoc.has_value());532 auto InstLoc = MaybeInstLoc.value();533 ASSERT_EQ_LOC(InstLoc, ExpectedLoc);534 }535}536 537} // end anonymous namespace538