File indexing completed on 2026-05-10 08:37:10
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014 #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_SYMBOLMANAGER_H
0015 #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_SYMBOLMANAGER_H
0016
0017 #include "clang/AST/Expr.h"
0018 #include "clang/AST/Type.h"
0019 #include "clang/Analysis/AnalysisDeclContext.h"
0020 #include "clang/Basic/LLVM.h"
0021 #include "clang/StaticAnalyzer/Core/PathSensitive/APSIntPtr.h"
0022 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
0023 #include "clang/StaticAnalyzer/Core/PathSensitive/StoreRef.h"
0024 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
0025 #include "llvm/ADT/DenseMap.h"
0026 #include "llvm/ADT/DenseSet.h"
0027 #include "llvm/ADT/FoldingSet.h"
0028 #include "llvm/ADT/ImmutableSet.h"
0029 #include "llvm/ADT/iterator_range.h"
0030 #include "llvm/Support/Allocator.h"
0031 #include <cassert>
0032
0033 namespace clang {
0034
0035 class ASTContext;
0036 class Stmt;
0037
0038 namespace ento {
0039
0040 class BasicValueFactory;
0041 class StoreManager;
0042
0043
0044 class SymbolRegionValue : public SymbolData {
0045 const TypedValueRegion *R;
0046
0047 friend class SymExprAllocator;
0048 SymbolRegionValue(SymbolID sym, const TypedValueRegion *r)
0049 : SymbolData(SymbolRegionValueKind, sym), R(r) {
0050 assert(r);
0051 assert(isValidTypeForSymbol(r->getValueType()));
0052 }
0053
0054 public:
0055 LLVM_ATTRIBUTE_RETURNS_NONNULL
0056 const TypedValueRegion *getRegion() const { return R; }
0057
0058 static void Profile(llvm::FoldingSetNodeID& profile, const TypedValueRegion* R) {
0059 profile.AddInteger((unsigned) SymbolRegionValueKind);
0060 profile.AddPointer(R);
0061 }
0062
0063 void Profile(llvm::FoldingSetNodeID& profile) override {
0064 Profile(profile, R);
0065 }
0066
0067 StringRef getKindStr() const override;
0068
0069 void dumpToStream(raw_ostream &os) const override;
0070 const MemRegion *getOriginRegion() const override { return getRegion(); }
0071
0072 QualType getType() const override;
0073
0074
0075 static bool classof(const SymExpr *SE) {
0076 return SE->getKind() == SymbolRegionValueKind;
0077 }
0078 };
0079
0080
0081
0082 class SymbolConjured : public SymbolData {
0083 const Stmt *S;
0084 QualType T;
0085 unsigned Count;
0086 const LocationContext *LCtx;
0087 const void *SymbolTag;
0088
0089 friend class SymExprAllocator;
0090 SymbolConjured(SymbolID sym, const Stmt *s, const LocationContext *lctx,
0091 QualType t, unsigned count, const void *symbolTag)
0092 : SymbolData(SymbolConjuredKind, sym), S(s), T(t), Count(count),
0093 LCtx(lctx), SymbolTag(symbolTag) {
0094
0095
0096
0097
0098
0099 assert(lctx);
0100 assert(isValidTypeForSymbol(t));
0101 }
0102
0103 public:
0104
0105 const Stmt *getStmt() const { return S; }
0106 unsigned getCount() const { return Count; }
0107
0108 const void *getTag() const { return SymbolTag; }
0109
0110 QualType getType() const override;
0111
0112 StringRef getKindStr() const override;
0113
0114 void dumpToStream(raw_ostream &os) const override;
0115
0116 static void Profile(llvm::FoldingSetNodeID &profile, const Stmt *S,
0117 const LocationContext *LCtx, QualType T, unsigned Count,
0118 const void *SymbolTag) {
0119 profile.AddInteger((unsigned)SymbolConjuredKind);
0120 profile.AddPointer(S);
0121 profile.AddPointer(LCtx);
0122 profile.Add(T);
0123 profile.AddInteger(Count);
0124 profile.AddPointer(SymbolTag);
0125 }
0126
0127 void Profile(llvm::FoldingSetNodeID& profile) override {
0128 Profile(profile, S, LCtx, T, Count, SymbolTag);
0129 }
0130
0131
0132 static bool classof(const SymExpr *SE) {
0133 return SE->getKind() == SymbolConjuredKind;
0134 }
0135 };
0136
0137
0138
0139 class SymbolDerived : public SymbolData {
0140 SymbolRef parentSymbol;
0141 const TypedValueRegion *R;
0142
0143 friend class SymExprAllocator;
0144 SymbolDerived(SymbolID sym, SymbolRef parent, const TypedValueRegion *r)
0145 : SymbolData(SymbolDerivedKind, sym), parentSymbol(parent), R(r) {
0146 assert(parent);
0147 assert(r);
0148 assert(isValidTypeForSymbol(r->getValueType()));
0149 }
0150
0151 public:
0152 LLVM_ATTRIBUTE_RETURNS_NONNULL
0153 SymbolRef getParentSymbol() const { return parentSymbol; }
0154 LLVM_ATTRIBUTE_RETURNS_NONNULL
0155 const TypedValueRegion *getRegion() const { return R; }
0156
0157 QualType getType() const override;
0158
0159 StringRef getKindStr() const override;
0160
0161 void dumpToStream(raw_ostream &os) const override;
0162 const MemRegion *getOriginRegion() const override { return getRegion(); }
0163
0164 static void Profile(llvm::FoldingSetNodeID& profile, SymbolRef parent,
0165 const TypedValueRegion *r) {
0166 profile.AddInteger((unsigned) SymbolDerivedKind);
0167 profile.AddPointer(r);
0168 profile.AddPointer(parent);
0169 }
0170
0171 void Profile(llvm::FoldingSetNodeID& profile) override {
0172 Profile(profile, parentSymbol, R);
0173 }
0174
0175
0176 static bool classof(const SymExpr *SE) {
0177 return SE->getKind() == SymbolDerivedKind;
0178 }
0179 };
0180
0181
0182
0183
0184 class SymbolExtent : public SymbolData {
0185 const SubRegion *R;
0186
0187 friend class SymExprAllocator;
0188 SymbolExtent(SymbolID sym, const SubRegion *r)
0189 : SymbolData(SymbolExtentKind, sym), R(r) {
0190 assert(r);
0191 }
0192
0193 public:
0194 LLVM_ATTRIBUTE_RETURNS_NONNULL
0195 const SubRegion *getRegion() const { return R; }
0196
0197 QualType getType() const override;
0198
0199 StringRef getKindStr() const override;
0200
0201 void dumpToStream(raw_ostream &os) const override;
0202
0203 static void Profile(llvm::FoldingSetNodeID& profile, const SubRegion *R) {
0204 profile.AddInteger((unsigned) SymbolExtentKind);
0205 profile.AddPointer(R);
0206 }
0207
0208 void Profile(llvm::FoldingSetNodeID& profile) override {
0209 Profile(profile, R);
0210 }
0211
0212
0213 static bool classof(const SymExpr *SE) {
0214 return SE->getKind() == SymbolExtentKind;
0215 }
0216 };
0217
0218
0219
0220
0221
0222 class SymbolMetadata : public SymbolData {
0223 const MemRegion* R;
0224 const Stmt *S;
0225 QualType T;
0226 const LocationContext *LCtx;
0227
0228
0229 unsigned Count;
0230 const void *Tag;
0231
0232 friend class SymExprAllocator;
0233 SymbolMetadata(SymbolID sym, const MemRegion* r, const Stmt *s, QualType t,
0234 const LocationContext *LCtx, unsigned count, const void *tag)
0235 : SymbolData(SymbolMetadataKind, sym), R(r), S(s), T(t), LCtx(LCtx),
0236 Count(count), Tag(tag) {
0237 assert(r);
0238 assert(s);
0239 assert(isValidTypeForSymbol(t));
0240 assert(LCtx);
0241 assert(tag);
0242 }
0243
0244 public:
0245 LLVM_ATTRIBUTE_RETURNS_NONNULL
0246 const MemRegion *getRegion() const { return R; }
0247
0248 LLVM_ATTRIBUTE_RETURNS_NONNULL
0249 const Stmt *getStmt() const { return S; }
0250
0251 LLVM_ATTRIBUTE_RETURNS_NONNULL
0252 const LocationContext *getLocationContext() const { return LCtx; }
0253
0254 unsigned getCount() const { return Count; }
0255
0256 LLVM_ATTRIBUTE_RETURNS_NONNULL
0257 const void *getTag() const { return Tag; }
0258
0259 QualType getType() const override;
0260
0261 StringRef getKindStr() const override;
0262
0263 void dumpToStream(raw_ostream &os) const override;
0264
0265 static void Profile(llvm::FoldingSetNodeID &profile, const MemRegion *R,
0266 const Stmt *S, QualType T, const LocationContext *LCtx,
0267 unsigned Count, const void *Tag) {
0268 profile.AddInteger((unsigned)SymbolMetadataKind);
0269 profile.AddPointer(R);
0270 profile.AddPointer(S);
0271 profile.Add(T);
0272 profile.AddPointer(LCtx);
0273 profile.AddInteger(Count);
0274 profile.AddPointer(Tag);
0275 }
0276
0277 void Profile(llvm::FoldingSetNodeID& profile) override {
0278 Profile(profile, R, S, T, LCtx, Count, Tag);
0279 }
0280
0281
0282 static bool classof(const SymExpr *SE) {
0283 return SE->getKind() == SymbolMetadataKind;
0284 }
0285 };
0286
0287
0288 class SymbolCast : public SymExpr {
0289 const SymExpr *Operand;
0290
0291
0292 QualType FromTy;
0293
0294
0295 QualType ToTy;
0296
0297 friend class SymExprAllocator;
0298 SymbolCast(SymbolID Sym, const SymExpr *In, QualType From, QualType To)
0299 : SymExpr(SymbolCastKind, Sym), Operand(In), FromTy(From), ToTy(To) {
0300 assert(In);
0301 assert(isValidTypeForSymbol(From));
0302
0303
0304 }
0305
0306 public:
0307 unsigned computeComplexity() const override {
0308 if (Complexity == 0)
0309 Complexity = 1 + Operand->computeComplexity();
0310 return Complexity;
0311 }
0312
0313 QualType getType() const override { return ToTy; }
0314
0315 LLVM_ATTRIBUTE_RETURNS_NONNULL
0316 const SymExpr *getOperand() const { return Operand; }
0317
0318 void dumpToStream(raw_ostream &os) const override;
0319
0320 static void Profile(llvm::FoldingSetNodeID& ID,
0321 const SymExpr *In, QualType From, QualType To) {
0322 ID.AddInteger((unsigned) SymbolCastKind);
0323 ID.AddPointer(In);
0324 ID.Add(From);
0325 ID.Add(To);
0326 }
0327
0328 void Profile(llvm::FoldingSetNodeID& ID) override {
0329 Profile(ID, Operand, FromTy, ToTy);
0330 }
0331
0332
0333 static bool classof(const SymExpr *SE) {
0334 return SE->getKind() == SymbolCastKind;
0335 }
0336 };
0337
0338
0339 class UnarySymExpr : public SymExpr {
0340 const SymExpr *Operand;
0341 UnaryOperator::Opcode Op;
0342 QualType T;
0343
0344 friend class SymExprAllocator;
0345 UnarySymExpr(SymbolID Sym, const SymExpr *In, UnaryOperator::Opcode Op,
0346 QualType T)
0347 : SymExpr(UnarySymExprKind, Sym), Operand(In), Op(Op), T(T) {
0348
0349
0350 assert((Op == UO_Minus || Op == UO_Not) && "non-supported unary expression");
0351
0352
0353
0354 assert(isValidTypeForSymbol(T) && "non-valid type for unary symbol");
0355 assert(!Loc::isLocType(T) && "unary symbol should be nonloc");
0356 }
0357
0358 public:
0359 unsigned computeComplexity() const override {
0360 if (Complexity == 0)
0361 Complexity = 1 + Operand->computeComplexity();
0362 return Complexity;
0363 }
0364
0365 const SymExpr *getOperand() const { return Operand; }
0366 UnaryOperator::Opcode getOpcode() const { return Op; }
0367 QualType getType() const override { return T; }
0368
0369 void dumpToStream(raw_ostream &os) const override;
0370
0371 static void Profile(llvm::FoldingSetNodeID &ID, const SymExpr *In,
0372 UnaryOperator::Opcode Op, QualType T) {
0373 ID.AddInteger((unsigned)UnarySymExprKind);
0374 ID.AddPointer(In);
0375 ID.AddInteger(Op);
0376 ID.Add(T);
0377 }
0378
0379 void Profile(llvm::FoldingSetNodeID &ID) override {
0380 Profile(ID, Operand, Op, T);
0381 }
0382
0383
0384 static bool classof(const SymExpr *SE) {
0385 return SE->getKind() == UnarySymExprKind;
0386 }
0387 };
0388
0389
0390 class BinarySymExpr : public SymExpr {
0391 BinaryOperator::Opcode Op;
0392 QualType T;
0393
0394 protected:
0395 BinarySymExpr(SymbolID Sym, Kind k, BinaryOperator::Opcode op, QualType t)
0396 : SymExpr(k, Sym), Op(op), T(t) {
0397 assert(classof(this));
0398
0399
0400
0401 assert(isValidTypeForSymbol(t) && !Loc::isLocType(t));
0402 }
0403
0404 public:
0405
0406
0407 QualType getType() const override { return T; }
0408
0409 BinaryOperator::Opcode getOpcode() const { return Op; }
0410
0411
0412 static bool classof(const SymExpr *SE) {
0413 Kind k = SE->getKind();
0414 return k >= BEGIN_BINARYSYMEXPRS && k <= END_BINARYSYMEXPRS;
0415 }
0416
0417 protected:
0418 static unsigned computeOperandComplexity(const SymExpr *Value) {
0419 return Value->computeComplexity();
0420 }
0421 static unsigned computeOperandComplexity(const llvm::APSInt &Value) {
0422 return 1;
0423 }
0424
0425 static const llvm::APSInt *getPointer(APSIntPtr Value) { return Value.get(); }
0426 static const SymExpr *getPointer(const SymExpr *Value) { return Value; }
0427
0428 static void dumpToStreamImpl(raw_ostream &os, const SymExpr *Value);
0429 static void dumpToStreamImpl(raw_ostream &os, const llvm::APSInt &Value);
0430 static void dumpToStreamImpl(raw_ostream &os, BinaryOperator::Opcode op);
0431 };
0432
0433
0434 template <class LHSTYPE, class RHSTYPE, SymExpr::Kind ClassKind>
0435 class BinarySymExprImpl : public BinarySymExpr {
0436 LHSTYPE LHS;
0437 RHSTYPE RHS;
0438
0439 friend class SymExprAllocator;
0440 BinarySymExprImpl(SymbolID Sym, LHSTYPE lhs, BinaryOperator::Opcode op,
0441 RHSTYPE rhs, QualType t)
0442 : BinarySymExpr(Sym, ClassKind, op, t), LHS(lhs), RHS(rhs) {
0443 assert(getPointer(lhs));
0444 assert(getPointer(rhs));
0445 }
0446
0447 public:
0448 void dumpToStream(raw_ostream &os) const override {
0449 dumpToStreamImpl(os, LHS);
0450 dumpToStreamImpl(os, getOpcode());
0451 dumpToStreamImpl(os, RHS);
0452 }
0453
0454 LHSTYPE getLHS() const { return LHS; }
0455 RHSTYPE getRHS() const { return RHS; }
0456
0457 unsigned computeComplexity() const override {
0458 if (Complexity == 0)
0459 Complexity =
0460 computeOperandComplexity(RHS) + computeOperandComplexity(LHS);
0461 return Complexity;
0462 }
0463
0464 static void Profile(llvm::FoldingSetNodeID &ID, LHSTYPE lhs,
0465 BinaryOperator::Opcode op, RHSTYPE rhs, QualType t) {
0466 ID.AddInteger((unsigned)ClassKind);
0467 ID.AddPointer(getPointer(lhs));
0468 ID.AddInteger(op);
0469 ID.AddPointer(getPointer(rhs));
0470 ID.Add(t);
0471 }
0472
0473 void Profile(llvm::FoldingSetNodeID &ID) override {
0474 Profile(ID, LHS, getOpcode(), RHS, getType());
0475 }
0476
0477
0478 static bool classof(const SymExpr *SE) { return SE->getKind() == ClassKind; }
0479 };
0480
0481
0482 using SymIntExpr = BinarySymExprImpl<const SymExpr *, APSIntPtr,
0483 SymExpr::Kind::SymIntExprKind>;
0484
0485
0486 using IntSymExpr = BinarySymExprImpl<APSIntPtr, const SymExpr *,
0487 SymExpr::Kind::IntSymExprKind>;
0488
0489
0490 using SymSymExpr = BinarySymExprImpl<const SymExpr *, const SymExpr *,
0491 SymExpr::Kind::SymSymExprKind>;
0492
0493 class SymExprAllocator {
0494 SymbolID NextSymbolID = 0;
0495 llvm::BumpPtrAllocator &Alloc;
0496
0497 public:
0498 explicit SymExprAllocator(llvm::BumpPtrAllocator &Alloc) : Alloc(Alloc) {}
0499
0500 template <class SymT, typename... ArgsT> SymT *make(ArgsT &&...Args) {
0501 return new (Alloc) SymT(nextID(), std::forward<ArgsT>(Args)...);
0502 }
0503
0504 private:
0505 SymbolID nextID() { return NextSymbolID++; }
0506 };
0507
0508 class SymbolManager {
0509 using DataSetTy = llvm::FoldingSet<SymExpr>;
0510 using SymbolDependTy =
0511 llvm::DenseMap<SymbolRef, std::unique_ptr<SymbolRefSmallVectorTy>>;
0512
0513 DataSetTy DataSet;
0514
0515
0516
0517 SymbolDependTy SymbolDependencies;
0518
0519 SymExprAllocator Alloc;
0520 BasicValueFactory &BV;
0521 ASTContext &Ctx;
0522
0523 public:
0524 SymbolManager(ASTContext &ctx, BasicValueFactory &bv,
0525 llvm::BumpPtrAllocator &bpalloc)
0526 : SymbolDependencies(16), Alloc(bpalloc), BV(bv), Ctx(ctx) {}
0527
0528 static bool canSymbolicate(QualType T);
0529
0530
0531
0532
0533 template <typename SymExprT, typename... Args>
0534 const SymExprT *acquire(Args &&...args);
0535
0536 const SymbolConjured *conjureSymbol(const Stmt *E,
0537 const LocationContext *LCtx, QualType T,
0538 unsigned VisitCount,
0539 const void *SymbolTag = nullptr) {
0540 return acquire<SymbolConjured>(E, LCtx, T, VisitCount, SymbolTag);
0541 }
0542
0543 const SymbolConjured* conjureSymbol(const Expr *E,
0544 const LocationContext *LCtx,
0545 unsigned VisitCount,
0546 const void *SymbolTag = nullptr) {
0547 return conjureSymbol(E, LCtx, E->getType(), VisitCount, SymbolTag);
0548 }
0549
0550 QualType getType(const SymExpr *SE) const {
0551 return SE->getType();
0552 }
0553
0554
0555
0556
0557 void addSymbolDependency(const SymbolRef Primary, const SymbolRef Dependent);
0558
0559 const SymbolRefSmallVectorTy *getDependentSymbols(const SymbolRef Primary);
0560
0561 ASTContext &getContext() { return Ctx; }
0562 BasicValueFactory &getBasicVals() { return BV; }
0563 };
0564
0565
0566 class SymbolReaper {
0567 enum SymbolStatus {
0568 NotProcessed,
0569 HaveMarkedDependents
0570 };
0571
0572 using SymbolSetTy = llvm::DenseSet<SymbolRef>;
0573 using SymbolMapTy = llvm::DenseMap<SymbolRef, SymbolStatus>;
0574 using RegionSetTy = llvm::DenseSet<const MemRegion *>;
0575
0576 SymbolMapTy TheLiving;
0577 SymbolSetTy MetadataInUse;
0578
0579 RegionSetTy LiveRegionRoots;
0580
0581
0582
0583
0584 RegionSetTy LazilyCopiedRegionRoots;
0585
0586 const StackFrameContext *LCtx;
0587 const Stmt *Loc;
0588 SymbolManager& SymMgr;
0589 StoreRef reapedStore;
0590 llvm::DenseMap<const MemRegion *, unsigned> includedRegionCache;
0591
0592 public:
0593
0594
0595
0596
0597
0598
0599
0600 SymbolReaper(const StackFrameContext *Ctx, const Stmt *s,
0601 SymbolManager &symmgr, StoreManager &storeMgr)
0602 : LCtx(Ctx), Loc(s), SymMgr(symmgr), reapedStore(nullptr, storeMgr) {}
0603
0604
0605 const LocationContext *getLocationContext() const { return LCtx; }
0606
0607 bool isLive(SymbolRef sym);
0608 bool isLiveRegion(const MemRegion *region);
0609 bool isLive(const Expr *ExprVal, const LocationContext *LCtx) const;
0610 bool isLive(const VarRegion *VR, bool includeStoreBindings = false) const;
0611
0612
0613
0614
0615
0616
0617 void markLive(SymbolRef sym);
0618
0619
0620
0621
0622
0623
0624
0625
0626 void markInUse(SymbolRef sym);
0627
0628 llvm::iterator_range<RegionSetTy::const_iterator> regions() const {
0629 return LiveRegionRoots;
0630 }
0631
0632
0633
0634
0635
0636 bool isDead(SymbolRef sym) {
0637 return !isLive(sym);
0638 }
0639
0640 void markLive(const MemRegion *region);
0641 void markLazilyCopied(const MemRegion *region);
0642 void markElementIndicesLive(const MemRegion *region);
0643
0644
0645
0646 void setReapedStore(StoreRef st) { reapedStore = st; }
0647
0648 private:
0649 bool isLazilyCopiedRegion(const MemRegion *region) const;
0650
0651
0652
0653 bool isReadableRegion(const MemRegion *region);
0654
0655
0656 void markDependentsLive(SymbolRef sym);
0657 };
0658
0659 class SymbolVisitor {
0660 protected:
0661 ~SymbolVisitor() = default;
0662
0663 public:
0664 SymbolVisitor() = default;
0665 SymbolVisitor(const SymbolVisitor &) = default;
0666 SymbolVisitor(SymbolVisitor &&) {}
0667
0668
0669
0670 SymbolVisitor &operator=(const SymbolVisitor &) = delete;
0671 SymbolVisitor &operator=(SymbolVisitor &&) = delete;
0672
0673
0674
0675
0676
0677 virtual bool VisitSymbol(SymbolRef sym) = 0;
0678 virtual bool VisitMemRegion(const MemRegion *) { return true; }
0679 };
0680
0681 template <typename T, typename... Args>
0682 const T *SymbolManager::acquire(Args &&...args) {
0683 llvm::FoldingSetNodeID profile;
0684 T::Profile(profile, args...);
0685 void *InsertPos;
0686 SymExpr *SD = DataSet.FindNodeOrInsertPos(profile, InsertPos);
0687 if (!SD) {
0688 SD = Alloc.make<T>(std::forward<Args>(args)...);
0689 DataSet.InsertNode(SD, InsertPos);
0690 }
0691 return cast<T>(SD);
0692 }
0693
0694 }
0695
0696 }
0697
0698
0699
0700
0701
0702
0703 template <>
0704 struct llvm::ImutContainerInfo<clang::ento::SymbolRef>
0705 : public ImutProfileInfo<clang::ento::SymbolRef> {
0706 using value_type = clang::ento::SymbolRef;
0707 using value_type_ref = clang::ento::SymbolRef;
0708 using key_type = value_type;
0709 using key_type_ref = value_type_ref;
0710 using data_type = bool;
0711 using data_type_ref = bool;
0712
0713 static key_type_ref KeyOfValue(value_type_ref D) { return D; }
0714 static data_type_ref DataOfValue(value_type_ref) { return true; }
0715
0716 static bool isEqual(clang::ento::SymbolRef LHS, clang::ento::SymbolRef RHS) {
0717 return LHS->getSymbolID() == RHS->getSymbolID();
0718 }
0719
0720 static bool isLess(clang::ento::SymbolRef LHS, clang::ento::SymbolRef RHS) {
0721 return LHS->getSymbolID() < RHS->getSymbolID();
0722 }
0723
0724
0725
0726
0727 static bool isDataEqual(data_type_ref, data_type_ref) { return true; }
0728 };
0729
0730 #endif