1//===-------------------- InterpBuiltinBitCast.cpp --------------*- C++ -*-===//
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-exception
6//
7//===----------------------------------------------------------------------===//
8#include "InterpBuiltinBitCast.h"
9#include "BitcastBuffer.h"
10#include "Boolean.h"
11#include "Char.h"
12#include "Context.h"
13#include "Floating.h"
14#include "Integral.h"
15#include "InterpState.h"
16#include "MemberPointer.h"
17#include "Pointer.h"
18#include "Record.h"
19#include "clang/AST/ASTContext.h"
20#include "clang/AST/RecordLayout.h"
21#include "clang/Basic/TargetInfo.h"
22
23#include <variant>
24
25using namespace clang;
26using namespace clang::interp;
27
28/// Implement __builtin_bit_cast and related operations.
29/// Since our internal representation for data is more complex than
30/// something we can simply memcpy or memcmp, we first bitcast all the data
31/// into a buffer, which we then later use to copy the data into the target.
32
33// TODO:
34// - Try to minimize heap allocations.
35// - Optimize the common case of only pushing and pulling full
36// bytes to/from the buffer.
37
38enum class Result { Success, Skip, Failure };
39
40/// Used to iterate over pointer fields.
41using DataFunc =
42 llvm::function_ref<Result(PtrView P, PrimType Ty, Bits BitOffset,
43 Bits FullBitWidth, bool PackedBools)>;
44
45#define BITCAST_TYPE_SWITCH(Expr, B) \
46 do { \
47 switch (Expr) { \
48 TYPE_SWITCH_CASE(PT_Sint8, B) \
49 TYPE_SWITCH_CASE(PT_Uint8, B) \
50 TYPE_SWITCH_CASE(PT_Sint16, B) \
51 TYPE_SWITCH_CASE(PT_Uint16, B) \
52 TYPE_SWITCH_CASE(PT_Sint32, B) \
53 TYPE_SWITCH_CASE(PT_Uint32, B) \
54 TYPE_SWITCH_CASE(PT_Sint64, B) \
55 TYPE_SWITCH_CASE(PT_Uint64, B) \
56 TYPE_SWITCH_CASE(PT_IntAP, B) \
57 TYPE_SWITCH_CASE(PT_IntAPS, B) \
58 TYPE_SWITCH_CASE(PT_Bool, B) \
59 default: \
60 llvm_unreachable("Unhandled bitcast type"); \
61 } \
62 } while (0)
63
64#define BITCAST_TYPE_SWITCH_FIXED_SIZE(Expr, B) \
65 do { \
66 switch (Expr) { \
67 TYPE_SWITCH_CASE(PT_Sint8, B) \
68 TYPE_SWITCH_CASE(PT_Uint8, B) \
69 TYPE_SWITCH_CASE(PT_Sint16, B) \
70 TYPE_SWITCH_CASE(PT_Uint16, B) \
71 TYPE_SWITCH_CASE(PT_Sint32, B) \
72 TYPE_SWITCH_CASE(PT_Uint32, B) \
73 TYPE_SWITCH_CASE(PT_Sint64, B) \
74 TYPE_SWITCH_CASE(PT_Uint64, B) \
75 TYPE_SWITCH_CASE(PT_Bool, B) \
76 default: \
77 llvm_unreachable("Unhandled bitcast type"); \
78 } \
79 } while (0)
80
81/// We use this to recursively iterate over all fields and elements of a pointer
82/// and extract relevant data for a bitcast.
83static Result enumerateData(PtrView P, const Context &Ctx, Bits Offset,
84 Bits BitsToRead, DataFunc F, bool Initialize) {
85 const Descriptor *FieldDesc = P.getFieldDesc();
86 assert(FieldDesc);
87
88 // Primitives.
89 if (FieldDesc->isPrimitive()) {
90 Bits FullBitWidth =
91 Bits(Ctx.getASTContext().getTypeSize(T: FieldDesc->getType()));
92 return F(P, FieldDesc->getPrimType(), Offset, FullBitWidth,
93 /*PackedBools=*/false);
94 }
95
96 // Primitive arrays.
97 if (FieldDesc->isPrimitiveArray()) {
98 QualType ElemType = FieldDesc->getElemQualType();
99 Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(T: ElemType));
100 PrimType ElemT = *Ctx.classify(T: ElemType);
101 // Special case, since the bools here are packed.
102 bool PackedBools =
103 FieldDesc->getType()->isPackedVectorBoolType(ctx: Ctx.getASTContext());
104 unsigned NumElems = FieldDesc->getNumElems();
105 bool Ok = true;
106 for (unsigned I = P.getIndex(); I != NumElems; ++I) {
107 Result Res = F(P.atIndex(Idx: I), ElemT, Offset, ElemSize, PackedBools);
108
109 Ok = Ok && (Res == Result::Success);
110 Offset += PackedBools ? Bits(1) : ElemSize;
111 if (Offset >= BitsToRead)
112 break;
113 }
114 return Ok ? Result::Success : Result::Skip;
115 }
116
117 // Composite arrays.
118 if (FieldDesc->isCompositeArray()) {
119 QualType ElemType = FieldDesc->getElemQualType();
120 Bits ElemSize = Bits(Ctx.getASTContext().getTypeSize(T: ElemType));
121 for (unsigned I = P.getIndex(); I != FieldDesc->getNumElems(); ++I) {
122 enumerateData(P: P.atIndex(Idx: I).narrow(), Ctx, Offset, BitsToRead, F,
123 Initialize);
124 Offset += ElemSize;
125 if (Offset >= BitsToRead)
126 break;
127 }
128 return Result::Success;
129 }
130
131 // Records.
132 if (FieldDesc->isRecord()) {
133 const Record *R = FieldDesc->ElemRecord;
134 if (R->getDecl()->isInvalidDecl())
135 return Result::Failure;
136 const ASTRecordLayout &Layout =
137 Ctx.getASTContext().getASTRecordLayout(D: R->getDecl());
138 bool Ok = true;
139
140 for (const Record::Field &Fi : R->fields()) {
141 if (Fi.isUnnamedBitField())
142 continue;
143
144 PtrView Elem = P.atField(Offset: Fi.Offset);
145 Bits BitOffset =
146 Offset + Bits(Layout.getFieldOffset(FieldNo: Fi.Decl->getFieldIndex()));
147 Result Res =
148 enumerateData(P: Elem, Ctx, Offset: BitOffset, BitsToRead, F, Initialize);
149 if (Initialize) {
150 if (Res == Result::Success)
151 Elem.initialize();
152 else if (Res == Result::Skip)
153 Elem.startLifetime();
154 }
155 Ok = Ok && Res != Result::Failure;
156 }
157 for (const Record::Base &B : R->bases()) {
158 PtrView Elem = P.atField(Offset: B.Offset);
159 if (!Initialize && !Elem.isInitialized())
160 return Result::Failure;
161
162 CharUnits ByteOffset =
163 Layout.getBaseClassOffset(Base: cast<CXXRecordDecl>(Val: B.Decl));
164 Bits BitOffset = Offset + Bits(Ctx.getASTContext().toBits(CharSize: ByteOffset));
165 Result Res =
166 enumerateData(P: Elem, Ctx, Offset: BitOffset, BitsToRead, F, Initialize);
167 if (Initialize) {
168 if (Res == Result::Success)
169 Elem.initialize();
170 else if (Res == Result::Skip)
171 Elem.startLifetime();
172 }
173 Ok = Ok && Res != Result::Failure;
174 }
175 return Ok ? Result::Success : Result::Failure;
176 }
177
178 llvm_unreachable("Unhandled data type");
179}
180
181static bool enumeratePointerFields(const Pointer &P, const Context &Ctx,
182 Bits BitsToRead, DataFunc F,
183 bool Initialize) {
184
185 return enumerateData(P: P.view(), Ctx, Offset: Bits::zero(), BitsToRead, F,
186 Initialize) != Result::Failure;
187}
188
189// This function is constexpr if and only if To, From, and the types of
190// all subobjects of To and From are types T such that...
191// (3.1) - is_union_v<T> is false;
192// (3.2) - is_pointer_v<T> is false;
193// (3.3) - is_member_pointer_v<T> is false;
194// (3.4) - is_volatile_v<T> is false; and
195// (3.5) - T has no non-static data members of reference type
196//
197// NOTE: This is a version of checkBitCastConstexprEligibilityType() in
198// ExprConstant.cpp.
199static bool CheckBitcastType(InterpState &S, CodePtr OpPC, QualType T,
200 bool IsToType) {
201 enum {
202 E_Union = 0,
203 E_Pointer,
204 E_MemberPointer,
205 E_Volatile,
206 E_Reference,
207 };
208 enum { C_Member, C_Base };
209
210 auto diag = [&](int Reason) -> bool {
211 const Expr *E = S.Current->getExpr(PC: OpPC);
212 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_invalid_type)
213 << static_cast<int>(IsToType) << (Reason == E_Reference) << Reason
214 << E->getSourceRange();
215 return false;
216 };
217 auto note = [&](int Construct, QualType NoteType,
218 SourceRange NoteRange) -> bool {
219 S.Note(Loc: NoteRange.getBegin(), DiagId: diag::note_constexpr_bit_cast_invalid_subtype)
220 << NoteType << Construct << T.getUnqualifiedType() << NoteRange;
221 return false;
222 };
223 auto unsupported = [&](QualType T) -> bool {
224 S.FFDiag(SI: S.Current->getSource(PC: OpPC),
225 DiagId: diag::note_constexpr_bit_cast_unsupported_type)
226 << T;
227 return false;
228 };
229
230 T = T.getCanonicalType();
231
232 if (T->isUnionType())
233 return diag(E_Union);
234 if (T->isPointerType())
235 return diag(E_Pointer);
236 if (T->isMemberPointerType())
237 return diag(E_MemberPointer);
238 if (T.isVolatileQualified())
239 return diag(E_Volatile);
240
241 if (const RecordDecl *RD = T->getAsRecordDecl()) {
242 if (const auto *CXXRD = dyn_cast<CXXRecordDecl>(Val: RD)) {
243 for (const CXXBaseSpecifier &BS : CXXRD->bases()) {
244 if (!CheckBitcastType(S, OpPC, T: BS.getType(), IsToType))
245 return note(C_Base, BS.getType(), BS.getBeginLoc());
246 }
247 }
248 for (const FieldDecl *FD : RD->fields()) {
249 if (FD->getType()->isReferenceType())
250 return diag(E_Reference);
251 if (!CheckBitcastType(S, OpPC, T: FD->getType(), IsToType))
252 return note(C_Member, FD->getType(), FD->getSourceRange());
253 }
254 }
255
256 if (T->isArrayType() &&
257 !CheckBitcastType(S, OpPC, T: S.getASTContext().getBaseElementType(QT: T),
258 IsToType))
259 return false;
260
261 if (const auto *VT = T->getAs<VectorType>()) {
262 const ASTContext &ASTCtx = S.getASTContext();
263 QualType EltTy = VT->getElementType();
264 unsigned NElts = VT->getNumElements();
265 unsigned EltSize =
266 VT->isPackedVectorBoolType(ctx: ASTCtx) ? 1 : ASTCtx.getTypeSize(T: EltTy);
267
268 if ((NElts * EltSize) % ASTCtx.getCharWidth() != 0) {
269 // The vector's size in bits is not a multiple of the target's byte size,
270 // so its layout is unspecified. For now, we'll simply treat these cases
271 // as unsupported (this should only be possible with OpenCL bool vectors
272 // whose element count isn't a multiple of the byte size).
273 const Expr *E = S.Current->getExpr(PC: OpPC);
274 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_invalid_vector)
275 << QualType(VT, 0) << EltSize << NElts << ASTCtx.getCharWidth();
276 return false;
277 }
278
279 if (EltTy->isRealFloatingType() &&
280 &ASTCtx.getFloatTypeSemantics(T: EltTy) == &APFloat::x87DoubleExtended()) {
281 // The layout for x86_fp80 vectors seems to be handled very inconsistently
282 // by both clang and LLVM, so for now we won't allow bit_casts involving
283 // it in a constexpr context.
284 return unsupported(EltTy);
285 }
286 }
287
288 if (T->isBlockPointerType())
289 return unsupported(T);
290
291 return true;
292}
293
294bool clang::interp::readPointerToBuffer(const Context &Ctx,
295 const Pointer &FromPtr,
296 BitcastBuffer &Buffer,
297 bool ReturnOnUninit) {
298 const ASTContext &ASTCtx = Ctx.getASTContext();
299 Endian TargetEndianness =
300 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
301
302 return enumeratePointerFields(
303 P: FromPtr, Ctx, BitsToRead: Buffer.size(),
304 F: [&](PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
305 bool PackedBools) -> Result {
306 Bits BitWidth = FullBitWidth;
307
308 if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
309 BitWidth = Bits(std::min(a: FD->getBitWidthValue(),
310 b: (unsigned)FullBitWidth.getQuantity()));
311 else if (T == PT_Bool && PackedBools)
312 BitWidth = Bits(1);
313
314 if (BitWidth.isZero())
315 return Result::Skip;
316
317 // Bits will be left uninitialized and diagnosed when reading.
318 if (!P.isInitialized())
319 return Result::Skip;
320
321 if (T == PT_Ptr) {
322 assert(P.getType()->isNullPtrType());
323 // Clang treats nullptr_t has having NO bits in its value
324 // representation. So, we accept it here and leave its bits
325 // uninitialized.
326 return Result::Skip;
327 }
328
329 assert(P.isInitialized());
330 auto Buff = std::make_unique<std::byte[]>(num: FullBitWidth.roundToBytes());
331 // Work around floating point types that contain unused padding bytes.
332 // This is really just `long double` on x86, which is the only
333 // fundamental type with padding bytes.
334 if (T == PT_Float) {
335 const Floating &F = P.deref<Floating>();
336 Bits NumBits = Bits(
337 llvm::APFloatBase::getSizeInBits(Sem: F.getAPFloat().getSemantics()));
338 assert(NumBits.isFullByte());
339 assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());
340 F.bitcastToMemory(Buff: Buff.get());
341 // Now, only (maybe) swap the actual size of the float, excluding
342 // the padding bits.
343 if (llvm::sys::IsBigEndianHost)
344 swapBytes(M: Buff.get(), N: NumBits.roundToBytes());
345
346 Buffer.markInitialized(Start: BitOffset, Length: NumBits);
347 } else {
348 BITCAST_TYPE_SWITCH(T, {
349 auto Val = P.deref<T>();
350 if (!Val.isNumber())
351 return Result::Failure;
352 Val.bitcastToMemory(Buff.get());
353 });
354
355 if (llvm::sys::IsBigEndianHost)
356 swapBytes(M: Buff.get(), N: FullBitWidth.roundToBytes());
357 Buffer.markInitialized(Start: BitOffset, Length: BitWidth);
358 }
359
360 Buffer.pushData(In: Buff.get(), BitOffset, BitWidth, TargetEndianness);
361 return Result::Success;
362 },
363 Initialize: false);
364}
365
366bool clang::interp::DoBitCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
367 std::byte *Buff, Bits BitWidth, Bits FullBitWidth,
368 bool &HasIndeterminateBits) {
369 assert(Ptr.isLive());
370 assert(Ptr.isBlockPointer());
371 assert(Buff);
372 assert(BitWidth <= FullBitWidth);
373 assert(FullBitWidth.isFullByte());
374 assert(BitWidth.isFullByte());
375
376 BitcastBuffer Buffer(FullBitWidth);
377 size_t BuffSize = FullBitWidth.roundToBytes();
378 QualType DataType = Ptr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
379 if (!CheckBitcastType(S, OpPC, T: DataType, /*IsToType=*/false))
380 return false;
381
382 bool Success = readPointerToBuffer(Ctx: S.getContext(), FromPtr: Ptr, Buffer,
383 /*ReturnOnUninit=*/false);
384 HasIndeterminateBits = !Buffer.rangeInitialized(Offset: Bits::zero(), Length: BitWidth);
385
386 const ASTContext &ASTCtx = S.getASTContext();
387 Endian TargetEndianness =
388 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
389 auto B =
390 Buffer.copyBits(BitOffset: Bits::zero(), BitWidth, FullBitWidth, TargetEndianness);
391
392 std::memcpy(dest: Buff, src: B.get(), n: BuffSize);
393
394 if (llvm::sys::IsBigEndianHost)
395 swapBytes(M: Buff, N: BitWidth.roundToBytes());
396
397 return Success;
398}
399bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
400 const Pointer &FromPtr, Pointer &ToPtr) {
401 const ASTContext &ASTCtx = S.getASTContext();
402 CharUnits ObjectReprChars = ASTCtx.getTypeSizeInChars(T: ToPtr.getType());
403
404 return DoBitCastPtr(S, OpPC, FromPtr, ToPtr, Size: ObjectReprChars.getQuantity());
405}
406
407bool clang::interp::DoBitCastPtr(InterpState &S, CodePtr OpPC,
408 const Pointer &FromPtr, Pointer &ToPtr,
409 size_t Size) {
410 assert(FromPtr.isLive());
411 assert(FromPtr.isBlockPointer());
412 assert(ToPtr.isBlockPointer());
413
414 QualType FromType = FromPtr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
415 QualType ToType = ToPtr.getFieldDesc()->getDataType(Ctx: S.getASTContext());
416
417 if (!CheckBitcastType(S, OpPC, T: ToType, /*IsToType=*/true))
418 return false;
419 if (!CheckBitcastType(S, OpPC, T: FromType, /*IsToType=*/false))
420 return false;
421
422 const ASTContext &ASTCtx = S.getASTContext();
423 BitcastBuffer Buffer(Bytes(Size).toBits());
424 readPointerToBuffer(Ctx: S.getContext(), FromPtr, Buffer,
425 /*ReturnOnUninit=*/false);
426
427 // Now read the values out of the buffer again and into ToPtr.
428 Endian TargetEndianness =
429 ASTCtx.getTargetInfo().isLittleEndian() ? Endian::Little : Endian::Big;
430 bool Success = enumeratePointerFields(
431 P: ToPtr, Ctx: S.getContext(), BitsToRead: Buffer.size(),
432 F: [&](PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
433 bool PackedBools) -> Result {
434 QualType PtrType = P.getType();
435 if (T == PT_Float) {
436 const auto &Semantics = ASTCtx.getFloatTypeSemantics(T: PtrType);
437 Bits NumBits = Bits(llvm::APFloatBase::getSizeInBits(Sem: Semantics));
438 assert(NumBits.isFullByte());
439 assert(NumBits.getQuantity() <= FullBitWidth.getQuantity());
440 auto M = Buffer.copyBits(BitOffset, BitWidth: NumBits, FullBitWidth,
441 TargetEndianness);
442
443 if (llvm::sys::IsBigEndianHost)
444 swapBytes(M: M.get(), N: NumBits.roundToBytes());
445
446 Floating R = S.allocFloat(Sem: Semantics);
447 Floating::bitcastFromMemory(Buff: M.get(), Sem: Semantics, Result: &R);
448 P.deref<Floating>() = R;
449 P.initialize();
450 return Result::Success;
451 }
452
453 Bits BitWidth;
454 if (const FieldDecl *FD = P.getField(); FD && FD->isBitField())
455 BitWidth = Bits(std::min(a: FD->getBitWidthValue(),
456 b: (unsigned)FullBitWidth.getQuantity()));
457 else if (T == PT_Bool && PackedBools)
458 BitWidth = Bits(1);
459 else
460 BitWidth = FullBitWidth;
461
462 // If any of the bits are uninitialized, we need to abort unless the
463 // target type is std::byte or unsigned char.
464 bool Initialized = Buffer.rangeInitialized(Offset: BitOffset, Length: BitWidth);
465 if (!Initialized) {
466 if (!PtrType->isStdByteType() &&
467 !PtrType->isSpecificBuiltinType(K: BuiltinType::UChar) &&
468 !PtrType->isSpecificBuiltinType(K: BuiltinType::Char_U)) {
469 const Expr *E = S.Current->getExpr(PC: OpPC);
470 S.FFDiag(E, DiagId: diag::note_constexpr_bit_cast_indet_dest)
471 << PtrType << S.getLangOpts().CharIsSigned
472 << E->getSourceRange();
473
474 return Result::Failure;
475 }
476 return Result::Skip;
477 }
478
479 auto Memory = Buffer.copyBits(BitOffset, BitWidth, FullBitWidth,
480 TargetEndianness);
481 if (llvm::sys::IsBigEndianHost)
482 swapBytes(M: Memory.get(), N: FullBitWidth.roundToBytes());
483
484 if (T == PT_IntAPS) {
485 P.deref<IntegralAP<true>>() =
486 S.allocAP<IntegralAP<true>>(BitWidth: FullBitWidth.getQuantity());
487 IntegralAP<true>::bitcastFromMemory(Src: Memory.get(),
488 BitWidth: FullBitWidth.getQuantity(),
489 Result: &P.deref<IntegralAP<true>>());
490 } else if (T == PT_IntAP) {
491 P.deref<IntegralAP<false>>() =
492 S.allocAP<IntegralAP<false>>(BitWidth: FullBitWidth.getQuantity());
493 IntegralAP<false>::bitcastFromMemory(Src: Memory.get(),
494 BitWidth: FullBitWidth.getQuantity(),
495 Result: &P.deref<IntegralAP<false>>());
496 } else {
497 BITCAST_TYPE_SWITCH_FIXED_SIZE(T, {
498 if (BitWidth.nonZero())
499 P.deref<T>() = T::bitcastFromMemory(Memory.get(), T::bitWidth())
500 .truncate(BitWidth.getQuantity());
501 else
502 P.deref<T>() = T::zero();
503 });
504 }
505 P.initialize();
506 return Result::Success;
507 },
508 Initialize: true);
509
510 return Success;
511}
512
513using PrimTypeVariant =
514 std::variant<Pointer, FunctionPointer, MemberPointer, FixedPoint,
515 Char<false>, Char<true>, Integral<16, false>,
516 Integral<16, true>, Integral<32, false>, Integral<32, true>,
517 Integral<64, false>, Integral<64, true>, IntegralAP<true>,
518 IntegralAP<false>, Boolean, Floating>;
519
520// NB: This implementation isn't exactly ideal, but:
521// 1) We can't just do a bitcast here since we need to be able to
522// copy pointers.
523// 2) This also needs to handle overlapping regions.
524// 3) We currently have no way of iterating over the fields of a pointer
525// backwards.
526bool clang::interp::DoMemcpy(InterpState &S, CodePtr OpPC,
527 const Pointer &SrcPtr, const Pointer &DestPtr,
528 Bits Size) {
529 assert(SrcPtr.isBlockPointer());
530 assert(DestPtr.isBlockPointer());
531
532 llvm::SmallVector<PrimTypeVariant> Values;
533 enumeratePointerFields(
534 P: SrcPtr, Ctx: S.getContext(), BitsToRead: Size,
535 F: [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
536 bool PackedBools) -> Result {
537 TYPE_SWITCH(T, { Values.push_back(P.deref<T>()); });
538 return Result::Success;
539 },
540 Initialize: false);
541
542 unsigned ValueIndex = 0;
543 enumeratePointerFields(
544 P: DestPtr, Ctx: S.getContext(), BitsToRead: Size,
545 F: [&](const PtrView P, PrimType T, Bits BitOffset, Bits FullBitWidth,
546 bool PackedBools) -> Result {
547 TYPE_SWITCH(T, {
548 P.deref<T>() = std::get<T>(Values[ValueIndex]);
549 P.initialize();
550 });
551
552 ++ValueIndex;
553 return Result::Success;
554 },
555 Initialize: true);
556
557 // We should've read all the values into DestPtr.
558 assert(ValueIndex == Values.size());
559
560 return true;
561}
562