1//===-- TypeRecord.cpp ------------------------------------------*- C++ -*-===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9
10#include "llvm/DebugInfo/CodeView/TypeRecord.h"
11#include "llvm/DebugInfo/CodeView/TypeIndex.h"
12#include "llvm/DebugInfo/CodeView/RecordSerialization.h"
13
14using namespace llvm;
15using namespace llvm::codeview;
16
17//===----------------------------------------------------------------------===//
18// Type record deserialization
19//===----------------------------------------------------------------------===//
20
21ErrorOr<MemberPointerInfo>
22MemberPointerInfo::deserialize(ArrayRef<uint8_t> &Data) {
23  const Layout *L = nullptr;
24  if (auto EC = consumeObject(Data, L))
25    return EC;
26
27  TypeIndex T = L->ClassType;
28  uint16_t R = L->Representation;
29  PointerToMemberRepresentation PMR =
30      static_cast<PointerToMemberRepresentation>(R);
31  return MemberPointerInfo(T, PMR);
32}
33
34ErrorOr<ModifierRecord> ModifierRecord::deserialize(TypeRecordKind Kind,
35                                                    ArrayRef<uint8_t> &Data) {
36  const Layout *L = nullptr;
37  if (auto EC = consumeObject(Data, L))
38    return EC;
39
40  TypeIndex M = L->ModifiedType;
41  uint16_t O = L->Modifiers;
42  ModifierOptions MO = static_cast<ModifierOptions>(O);
43  return ModifierRecord(M, MO);
44}
45
46ErrorOr<ProcedureRecord> ProcedureRecord::deserialize(TypeRecordKind Kind,
47                                                      ArrayRef<uint8_t> &Data) {
48  const Layout *L = nullptr;
49  if (auto EC = consumeObject(Data, L))
50    return EC;
51  return ProcedureRecord(L->ReturnType, L->CallConv, L->Options,
52                         L->NumParameters, L->ArgListType);
53}
54
55ErrorOr<MemberFunctionRecord>
56MemberFunctionRecord::deserialize(TypeRecordKind Kind,
57                                  ArrayRef<uint8_t> &Data) {
58  const Layout *L = nullptr;
59  CV_DESERIALIZE(Data, L);
60  return MemberFunctionRecord(L->ReturnType, L->ClassType, L->ThisType,
61                              L->CallConv, L->Options, L->NumParameters,
62                              L->ArgListType, L->ThisAdjustment);
63}
64
65ErrorOr<MemberFuncIdRecord>
66MemberFuncIdRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
67  const Layout *L = nullptr;
68  StringRef Name;
69  CV_DESERIALIZE(Data, L, Name);
70  return MemberFuncIdRecord(L->ClassType, L->FunctionType, Name);
71}
72
73ErrorOr<ArgListRecord> ArgListRecord::deserialize(TypeRecordKind Kind,
74                                                  ArrayRef<uint8_t> &Data) {
75  if (Kind != TypeRecordKind::StringList && Kind != TypeRecordKind::ArgList)
76    return std::make_error_code(std::errc::illegal_byte_sequence);
77
78  const Layout *L = nullptr;
79  ArrayRef<TypeIndex> Indices;
80  CV_DESERIALIZE(Data, L, CV_ARRAY_FIELD_N(Indices, L->NumArgs));
81  return ArgListRecord(Kind, Indices);
82}
83
84ErrorOr<PointerRecord> PointerRecord::deserialize(TypeRecordKind Kind,
85                                                  ArrayRef<uint8_t> &Data) {
86  const Layout *L = nullptr;
87  if (auto EC = consumeObject(Data, L))
88    return EC;
89
90  PointerKind PtrKind = L->getPtrKind();
91  PointerMode Mode = L->getPtrMode();
92  uint32_t Opts = L->Attrs;
93  PointerOptions Options = static_cast<PointerOptions>(Opts);
94  uint8_t Size = L->getPtrSize();
95
96  if (L->isPointerToMember()) {
97    auto E = MemberPointerInfo::deserialize(Data);
98    if (E.getError())
99      return std::make_error_code(std::errc::illegal_byte_sequence);
100    return PointerRecord(L->PointeeType, PtrKind, Mode, Options, Size, *E);
101  }
102
103  return PointerRecord(L->PointeeType, PtrKind, Mode, Options, Size);
104}
105
106ErrorOr<NestedTypeRecord>
107NestedTypeRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
108  const Layout *L = nullptr;
109  StringRef Name;
110  CV_DESERIALIZE(Data, L, Name);
111  return NestedTypeRecord(L->Type, Name);
112}
113
114ErrorOr<ArrayRecord> ArrayRecord::deserialize(TypeRecordKind Kind,
115                                              ArrayRef<uint8_t> &Data) {
116  const Layout *L = nullptr;
117  uint64_t Size;
118  StringRef Name;
119  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Size), Name);
120  return ArrayRecord(L->ElementType, L->IndexType, Size, Name);
121}
122
123ErrorOr<ClassRecord> ClassRecord::deserialize(TypeRecordKind Kind,
124                                              ArrayRef<uint8_t> &Data) {
125  uint64_t Size = 0;
126  StringRef Name;
127  StringRef UniqueName;
128  uint16_t Props;
129  const Layout *L = nullptr;
130
131  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Size), Name,
132                 CV_CONDITIONAL_FIELD(UniqueName, L->hasUniqueName()));
133
134  Props = L->Properties;
135  uint16_t WrtValue = (Props & WinRTKindMask) >> WinRTKindShift;
136  WindowsRTClassKind WRT = static_cast<WindowsRTClassKind>(WrtValue);
137  uint16_t HfaMask = (Props & HfaKindMask) >> HfaKindShift;
138  HfaKind Hfa = static_cast<HfaKind>(HfaMask);
139
140  ClassOptions Options = static_cast<ClassOptions>(Props);
141  return ClassRecord(Kind, L->MemberCount, Options, Hfa, WRT, L->FieldList,
142                     L->DerivedFrom, L->VShape, Size, Name, UniqueName);
143}
144
145ErrorOr<UnionRecord> UnionRecord::deserialize(TypeRecordKind Kind,
146                                              ArrayRef<uint8_t> &Data) {
147  uint64_t Size = 0;
148  StringRef Name;
149  StringRef UniqueName;
150  uint16_t Props;
151
152  const Layout *L = nullptr;
153  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Size), Name,
154                 CV_CONDITIONAL_FIELD(UniqueName, L->hasUniqueName()));
155
156  Props = L->Properties;
157
158  uint16_t HfaMask = (Props & HfaKindMask) >> HfaKindShift;
159  HfaKind Hfa = static_cast<HfaKind>(HfaMask);
160  ClassOptions Options = static_cast<ClassOptions>(Props);
161  return UnionRecord(L->MemberCount, Options, Hfa, L->FieldList, Size, Name,
162                     UniqueName);
163}
164
165ErrorOr<EnumRecord> EnumRecord::deserialize(TypeRecordKind Kind,
166                                            ArrayRef<uint8_t> &Data) {
167  const Layout *L = nullptr;
168  StringRef Name;
169  StringRef UniqueName;
170  CV_DESERIALIZE(Data, L, Name,
171                 CV_CONDITIONAL_FIELD(UniqueName, L->hasUniqueName()));
172
173  uint16_t P = L->Properties;
174  ClassOptions Options = static_cast<ClassOptions>(P);
175  return EnumRecord(L->NumEnumerators, Options, L->FieldListType, Name,
176                    UniqueName, L->UnderlyingType);
177}
178
179ErrorOr<BitFieldRecord> BitFieldRecord::deserialize(TypeRecordKind Kind,
180                                                    ArrayRef<uint8_t> &Data) {
181  const Layout *L = nullptr;
182  CV_DESERIALIZE(Data, L);
183  return BitFieldRecord(L->Type, L->BitSize, L->BitOffset);
184}
185
186ErrorOr<VFTableShapeRecord>
187VFTableShapeRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
188  const Layout *L = nullptr;
189  if (auto EC = consumeObject(Data, L))
190    return EC;
191
192  std::vector<VFTableSlotKind> Slots;
193  uint16_t Count = L->VFEntryCount;
194  while (Count > 0) {
195    if (Data.empty())
196      return std::make_error_code(std::errc::illegal_byte_sequence);
197
198    // Process up to 2 nibbles at a time (if there are at least 2 remaining)
199    uint8_t Value = Data[0] & 0x0F;
200    Slots.push_back(static_cast<VFTableSlotKind>(Value));
201    if (--Count > 0) {
202      Value = (Data[0] & 0xF0) >> 4;
203      Slots.push_back(static_cast<VFTableSlotKind>(Value));
204      --Count;
205    }
206    Data = Data.slice(1);
207  }
208
209  return VFTableShapeRecord(Slots);
210}
211
212ErrorOr<TypeServer2Record>
213TypeServer2Record::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
214  const Layout *L = nullptr;
215  StringRef Name;
216  CV_DESERIALIZE(Data, L, Name);
217  return TypeServer2Record(StringRef(L->Guid, 16), L->Age, Name);
218}
219
220ErrorOr<StringIdRecord> StringIdRecord::deserialize(TypeRecordKind Kind,
221                                                    ArrayRef<uint8_t> &Data) {
222  const Layout *L = nullptr;
223  StringRef Name;
224  CV_DESERIALIZE(Data, L, Name);
225  return StringIdRecord(L->id, Name);
226}
227
228ErrorOr<FuncIdRecord> FuncIdRecord::deserialize(TypeRecordKind Kind,
229                                                ArrayRef<uint8_t> &Data) {
230  const Layout *L = nullptr;
231  StringRef Name;
232  CV_DESERIALIZE(Data, L, Name);
233  return FuncIdRecord(L->ParentScope, L->FunctionType, Name);
234}
235
236ErrorOr<UdtSourceLineRecord>
237UdtSourceLineRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
238  const Layout *L = nullptr;
239  CV_DESERIALIZE(Data, L);
240  return UdtSourceLineRecord(L->UDT, L->SourceFile, L->LineNumber);
241}
242
243ErrorOr<BuildInfoRecord> BuildInfoRecord::deserialize(TypeRecordKind Kind,
244                                                      ArrayRef<uint8_t> &Data) {
245  const Layout *L = nullptr;
246  ArrayRef<TypeIndex> Indices;
247  CV_DESERIALIZE(Data, L, CV_ARRAY_FIELD_N(Indices, L->NumArgs));
248  return BuildInfoRecord(Indices);
249}
250
251ErrorOr<VFTableRecord> VFTableRecord::deserialize(TypeRecordKind Kind,
252                                                  ArrayRef<uint8_t> &Data) {
253  const Layout *L = nullptr;
254  StringRef Name;
255  std::vector<StringRef> Names;
256  CV_DESERIALIZE(Data, L, Name, CV_ARRAY_FIELD_TAIL(Names));
257  return VFTableRecord(L->CompleteClass, L->OverriddenVFTable, L->VFPtrOffset,
258                       Name, Names);
259}
260
261ErrorOr<OneMethodRecord> OneMethodRecord::deserialize(TypeRecordKind Kind,
262                                                      ArrayRef<uint8_t> &Data) {
263  const Layout *L = nullptr;
264  StringRef Name;
265  int32_t VFTableOffset = -1;
266
267  CV_DESERIALIZE(Data, L, CV_CONDITIONAL_FIELD(VFTableOffset,
268                                               L->Attrs.isIntroducedVirtual()),
269                 Name);
270
271  MethodOptions Options = L->Attrs.getFlags();
272  MethodKind MethKind = L->Attrs.getMethodKind();
273  MemberAccess Access = L->Attrs.getAccess();
274  OneMethodRecord Method(L->Type, MethKind, Options, Access, VFTableOffset,
275                         Name);
276  // Validate the vftable offset.
277  if (Method.isIntroducingVirtual() && Method.getVFTableOffset() < 0)
278    return std::make_error_code(std::errc::illegal_byte_sequence);
279  return Method;
280}
281
282ErrorOr<MethodOverloadListRecord>
283MethodOverloadListRecord::deserialize(TypeRecordKind Kind,
284                                      ArrayRef<uint8_t> &Data) {
285  std::vector<OneMethodRecord> Methods;
286  while (!Data.empty()) {
287    const Layout *L = nullptr;
288    int32_t VFTableOffset = -1;
289    CV_DESERIALIZE(Data, L, CV_CONDITIONAL_FIELD(
290                                VFTableOffset, L->Attrs.isIntroducedVirtual()));
291
292    MethodOptions Options = L->Attrs.getFlags();
293    MethodKind MethKind = L->Attrs.getMethodKind();
294    MemberAccess Access = L->Attrs.getAccess();
295
296    Methods.emplace_back(L->Type, MethKind, Options, Access, VFTableOffset,
297                         StringRef());
298
299    // Validate the vftable offset.
300    auto &Method = Methods.back();
301    if (Method.isIntroducingVirtual() && Method.getVFTableOffset() < 0)
302      return std::make_error_code(std::errc::illegal_byte_sequence);
303  }
304  return MethodOverloadListRecord(Methods);
305}
306
307ErrorOr<OverloadedMethodRecord>
308OverloadedMethodRecord::deserialize(TypeRecordKind Kind,
309                                    ArrayRef<uint8_t> &Data) {
310  const Layout *L = nullptr;
311  StringRef Name;
312  CV_DESERIALIZE(Data, L, Name);
313  return OverloadedMethodRecord(L->MethodCount, L->MethList, Name);
314}
315
316ErrorOr<DataMemberRecord>
317DataMemberRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
318  const Layout *L = nullptr;
319  uint64_t Offset;
320  StringRef Name;
321  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Offset), Name);
322  return DataMemberRecord(L->Attrs.getAccess(), L->Type, Offset, Name);
323}
324
325ErrorOr<StaticDataMemberRecord>
326StaticDataMemberRecord::deserialize(TypeRecordKind Kind,
327                                    ArrayRef<uint8_t> &Data) {
328  const Layout *L = nullptr;
329  StringRef Name;
330  CV_DESERIALIZE(Data, L, Name);
331  return StaticDataMemberRecord(L->Attrs.getAccess(), L->Type, Name);
332}
333
334ErrorOr<EnumeratorRecord>
335EnumeratorRecord::deserialize(TypeRecordKind Kind, ArrayRef<uint8_t> &Data) {
336  const Layout *L = nullptr;
337  APSInt Value;
338  StringRef Name;
339  CV_DESERIALIZE(Data, L, Value, Name);
340  return EnumeratorRecord(L->Attrs.getAccess(), Value, Name);
341}
342
343ErrorOr<VFPtrRecord> VFPtrRecord::deserialize(TypeRecordKind Kind,
344                                              ArrayRef<uint8_t> &Data) {
345  const Layout *L = nullptr;
346  if (auto EC = consumeObject(Data, L))
347    return EC;
348  return VFPtrRecord(L->Type);
349}
350
351ErrorOr<BaseClassRecord> BaseClassRecord::deserialize(TypeRecordKind Kind,
352                                                      ArrayRef<uint8_t> &Data) {
353  const Layout *L = nullptr;
354  uint64_t Offset;
355  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Offset));
356  return BaseClassRecord(L->Attrs.getAccess(), L->BaseType, Offset);
357}
358
359ErrorOr<VirtualBaseClassRecord>
360VirtualBaseClassRecord::deserialize(TypeRecordKind Kind,
361                                    ArrayRef<uint8_t> &Data) {
362  const Layout *L = nullptr;
363  uint64_t Offset;
364  uint64_t Index;
365  CV_DESERIALIZE(Data, L, CV_NUMERIC_FIELD(Offset), CV_NUMERIC_FIELD(Index));
366  return VirtualBaseClassRecord(L->Attrs.getAccess(), L->BaseType, L->VBPtrType,
367                                Offset, Index);
368}
369
370ErrorOr<ListContinuationRecord>
371ListContinuationRecord::deserialize(TypeRecordKind Kind,
372                                    ArrayRef<uint8_t> &Data) {
373  const Layout *L = nullptr;
374  CV_DESERIALIZE(Data, L);
375  return ListContinuationRecord(L->ContinuationIndex);
376}
377
378//===----------------------------------------------------------------------===//
379// Type index remapping
380//===----------------------------------------------------------------------===//
381
382static bool remapIndex(ArrayRef<TypeIndex> IndexMap, TypeIndex &Idx) {
383  // Simple types are unchanged.
384  if (Idx.isSimple())
385    return true;
386  unsigned MapPos = Idx.getIndex() - TypeIndex::FirstNonSimpleIndex;
387  if (MapPos < IndexMap.size()) {
388    Idx = IndexMap[MapPos];
389    return true;
390  }
391
392  // This type index is invalid. Remap this to "not translated by cvpack",
393  // and return failure.
394  Idx = TypeIndex(SimpleTypeKind::NotTranslated, SimpleTypeMode::Direct);
395  return false;
396}
397
398bool ModifierRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
399  return remapIndex(IndexMap, ModifiedType);
400}
401
402bool ProcedureRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
403  bool Success = true;
404  Success &= remapIndex(IndexMap, ReturnType);
405  Success &= remapIndex(IndexMap, ArgumentList);
406  return Success;
407}
408
409bool MemberFunctionRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
410  bool Success = true;
411  Success &= remapIndex(IndexMap, ReturnType);
412  Success &= remapIndex(IndexMap, ClassType);
413  Success &= remapIndex(IndexMap, ThisType);
414  Success &= remapIndex(IndexMap, ArgumentList);
415  return Success;
416}
417
418bool MemberFuncIdRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
419  bool Success = true;
420  Success &= remapIndex(IndexMap, ClassType);
421  Success &= remapIndex(IndexMap, FunctionType);
422  return Success;
423}
424
425bool ArgListRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
426  bool Success = true;
427  for (TypeIndex &Str : StringIndices)
428    Success &= remapIndex(IndexMap, Str);
429  return Success;
430}
431
432bool MemberPointerInfo::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
433  return remapIndex(IndexMap, ContainingType);
434}
435
436bool PointerRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
437  bool Success = true;
438  Success &= remapIndex(IndexMap, ReferentType);
439  if (isPointerToMember())
440    Success &= MemberInfo.remapTypeIndices(IndexMap);
441  return Success;
442}
443
444bool NestedTypeRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
445  return remapIndex(IndexMap, Type);
446}
447
448bool ArrayRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
449  bool Success = true;
450  Success &= remapIndex(IndexMap, ElementType);
451  Success &= remapIndex(IndexMap, IndexType);
452  return Success;
453}
454
455bool TagRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
456  return remapIndex(IndexMap, FieldList);
457}
458
459bool ClassRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
460  bool Success = true;
461  Success &= TagRecord::remapTypeIndices(IndexMap);
462  Success &= remapIndex(IndexMap, DerivationList);
463  Success &= remapIndex(IndexMap, VTableShape);
464  return Success;
465}
466
467bool EnumRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
468  bool Success = true;
469  Success &= TagRecord::remapTypeIndices(IndexMap);
470  Success &= remapIndex(IndexMap, UnderlyingType);
471  return Success;
472}
473
474bool BitFieldRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
475  return remapIndex(IndexMap, Type);
476}
477
478bool VFTableShapeRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
479  return true;
480}
481
482bool TypeServer2Record::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
483  return true;
484}
485
486bool StringIdRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
487  return remapIndex(IndexMap, Id);
488}
489
490bool FuncIdRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
491  bool Success = true;
492  Success &= remapIndex(IndexMap, ParentScope);
493  Success &= remapIndex(IndexMap, FunctionType);
494  return Success;
495}
496
497bool UdtSourceLineRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
498  bool Success = true;
499  Success &= remapIndex(IndexMap, UDT);
500  Success &= remapIndex(IndexMap, SourceFile);
501  return Success;
502}
503
504bool UdtModSourceLineRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
505  bool Success = true;
506  Success &= remapIndex(IndexMap, UDT);
507  Success &= remapIndex(IndexMap, SourceFile);
508  return Success;
509}
510
511bool BuildInfoRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
512  bool Success = true;
513  for (TypeIndex &Arg : ArgIndices)
514    Success &= remapIndex(IndexMap, Arg);
515  return Success;
516}
517
518bool VFTableRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
519  bool Success = true;
520  Success &= remapIndex(IndexMap, CompleteClass);
521  Success &= remapIndex(IndexMap, OverriddenVFTable);
522  return Success;
523}
524
525bool OneMethodRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
526  bool Success = true;
527  Success &= remapIndex(IndexMap, Type);
528  return Success;
529}
530
531bool MethodOverloadListRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
532  bool Success = true;
533  for (OneMethodRecord &Meth : Methods)
534    if ((Success = Meth.remapTypeIndices(IndexMap)))
535      return Success;
536  return Success;
537}
538
539bool OverloadedMethodRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
540  return remapIndex(IndexMap, MethodList);
541}
542
543bool DataMemberRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
544  return remapIndex(IndexMap, Type);
545}
546
547bool StaticDataMemberRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
548  return remapIndex(IndexMap, Type);
549}
550
551bool EnumeratorRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
552  return true;
553}
554
555bool VFPtrRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
556  return remapIndex(IndexMap, Type);
557}
558
559bool BaseClassRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
560  return remapIndex(IndexMap, Type);
561}
562
563bool VirtualBaseClassRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
564  bool Success = true;
565  Success &= remapIndex(IndexMap, BaseType);
566  Success &= remapIndex(IndexMap, VBPtrType);
567  return Success;
568}
569
570bool ListContinuationRecord::remapTypeIndices(ArrayRef<TypeIndex> IndexMap) {
571  return remapIndex(IndexMap, ContinuationIndex);
572}
573