File indexing completed on 2026-05-10 08:36:37
0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018 #ifndef LLVM_CLANG_AST_FORMATSTRING_H
0019 #define LLVM_CLANG_AST_FORMATSTRING_H
0020
0021 #include "clang/AST/CanonicalType.h"
0022 #include <optional>
0023
0024 namespace clang {
0025
0026 class TargetInfo;
0027
0028
0029
0030 namespace analyze_format_string {
0031
0032
0033
0034 class OptionalFlag {
0035 public:
0036 OptionalFlag(const char *Representation)
0037 : representation(Representation), flag(false) {}
0038 bool isSet() const { return flag; }
0039 void set() { flag = true; }
0040 void clear() { flag = false; }
0041 void setPosition(const char *position) {
0042 assert(position);
0043 flag = true;
0044 this->position = position;
0045 }
0046 const char *getPosition() const {
0047 assert(position);
0048 return position;
0049 }
0050 const char *toString() const { return representation; }
0051
0052
0053 explicit operator bool() const { return flag; }
0054 OptionalFlag& operator=(const bool &rhs) {
0055 flag = rhs;
0056 return *this;
0057 }
0058 private:
0059 const char *representation;
0060 const char *position;
0061 bool flag;
0062 };
0063
0064
0065 class LengthModifier {
0066 public:
0067 enum Kind {
0068 None,
0069 AsChar,
0070 AsShort,
0071 AsShortLong,
0072 AsLong,
0073 AsLongLong,
0074 AsQuad,
0075 AsIntMax,
0076 AsSizeT,
0077 AsPtrDiff,
0078 AsInt32,
0079 AsInt3264,
0080 AsInt64,
0081 AsLongDouble,
0082 AsAllocate,
0083 AsMAllocate,
0084 AsWide,
0085 AsWideChar = AsLong
0086 };
0087
0088 LengthModifier()
0089 : Position(nullptr), kind(None) {}
0090 LengthModifier(const char *pos, Kind k)
0091 : Position(pos), kind(k) {}
0092
0093 const char *getStart() const {
0094 return Position;
0095 }
0096
0097 unsigned getLength() const {
0098 switch (kind) {
0099 default:
0100 return 1;
0101 case AsLongLong:
0102 case AsChar:
0103 return 2;
0104 case AsInt32:
0105 case AsInt64:
0106 return 3;
0107 case None:
0108 return 0;
0109 }
0110 }
0111
0112 Kind getKind() const { return kind; }
0113 void setKind(Kind k) { kind = k; }
0114
0115 const char *toString() const;
0116
0117 private:
0118 const char *Position;
0119 Kind kind;
0120 };
0121
0122 class ConversionSpecifier {
0123 public:
0124 enum Kind {
0125 InvalidSpecifier = 0,
0126
0127 cArg,
0128 dArg,
0129 DArg,
0130 iArg,
0131
0132 bArg,
0133 BArg,
0134
0135 IntArgBeg = dArg,
0136 IntArgEnd = BArg,
0137
0138 oArg,
0139 OArg,
0140 uArg,
0141 UArg,
0142 xArg,
0143 XArg,
0144 UIntArgBeg = oArg,
0145 UIntArgEnd = XArg,
0146
0147 fArg,
0148 FArg,
0149 eArg,
0150 EArg,
0151 gArg,
0152 GArg,
0153 aArg,
0154 AArg,
0155 DoubleArgBeg = fArg,
0156 DoubleArgEnd = AArg,
0157
0158 sArg,
0159 pArg,
0160 nArg,
0161 PercentArg,
0162 CArg,
0163 SArg,
0164
0165
0166
0167
0168 PArg,
0169
0170
0171
0172 ZArg,
0173
0174
0175 kArg,
0176 KArg,
0177 rArg,
0178 RArg,
0179 FixedPointArgBeg = kArg,
0180 FixedPointArgEnd = RArg,
0181
0182
0183 ObjCObjArg,
0184 ObjCBeg = ObjCObjArg,
0185 ObjCEnd = ObjCObjArg,
0186
0187
0188 FreeBSDbArg,
0189 FreeBSDDArg,
0190 FreeBSDrArg,
0191 FreeBSDyArg,
0192
0193
0194 PrintErrno,
0195
0196 PrintfConvBeg = ObjCObjArg,
0197 PrintfConvEnd = PrintErrno,
0198
0199
0200 ScanListArg,
0201 ScanfConvBeg = ScanListArg,
0202 ScanfConvEnd = ScanListArg
0203 };
0204
0205 ConversionSpecifier(bool isPrintf = true)
0206 : IsPrintf(isPrintf), Position(nullptr), EndScanList(nullptr),
0207 kind(InvalidSpecifier) {}
0208
0209 ConversionSpecifier(bool isPrintf, const char *pos, Kind k)
0210 : IsPrintf(isPrintf), Position(pos), EndScanList(nullptr), kind(k) {}
0211
0212 const char *getStart() const {
0213 return Position;
0214 }
0215
0216 StringRef getCharacters() const {
0217 return StringRef(getStart(), getLength());
0218 }
0219
0220 bool consumesDataArgument() const {
0221 switch (kind) {
0222 case PrintErrno:
0223 assert(IsPrintf);
0224 return false;
0225 case PercentArg:
0226 return false;
0227 case InvalidSpecifier:
0228 return false;
0229 default:
0230 return true;
0231 }
0232 }
0233
0234 Kind getKind() const { return kind; }
0235 void setKind(Kind k) { kind = k; }
0236 unsigned getLength() const {
0237 return EndScanList ? EndScanList - Position : 1;
0238 }
0239 void setEndScanList(const char *pos) { EndScanList = pos; }
0240
0241 bool isIntArg() const { return (kind >= IntArgBeg && kind <= IntArgEnd) ||
0242 kind == FreeBSDrArg || kind == FreeBSDyArg; }
0243 bool isUIntArg() const { return kind >= UIntArgBeg && kind <= UIntArgEnd; }
0244 bool isAnyIntArg() const { return kind >= IntArgBeg && kind <= UIntArgEnd; }
0245 bool isDoubleArg() const {
0246 return kind >= DoubleArgBeg && kind <= DoubleArgEnd;
0247 }
0248 bool isFixedPointArg() const {
0249 return kind >= FixedPointArgBeg && kind <= FixedPointArgEnd;
0250 }
0251
0252 const char *toString() const;
0253
0254 bool isPrintfKind() const { return IsPrintf; }
0255
0256 std::optional<ConversionSpecifier> getStandardSpecifier() const;
0257
0258 protected:
0259 bool IsPrintf;
0260 const char *Position;
0261 const char *EndScanList;
0262 Kind kind;
0263 };
0264
0265 class ArgType {
0266 public:
0267 enum Kind { UnknownTy, InvalidTy, SpecificTy, ObjCPointerTy, CPointerTy,
0268 AnyCharTy, CStrTy, WCStrTy, WIntTy };
0269
0270
0271 enum MatchKind {
0272
0273
0274 NoMatch = 0,
0275
0276
0277 Match = 1,
0278
0279
0280 MatchPromotion,
0281
0282
0283 NoMatchPromotionTypeConfusion,
0284
0285
0286 NoMatchPedantic,
0287
0288 NoMatchSignedness,
0289
0290
0291 NoMatchTypeConfusion,
0292 };
0293
0294 private:
0295 const Kind K;
0296 QualType T;
0297 const char *Name = nullptr;
0298 bool Ptr = false;
0299
0300
0301
0302 enum class TypeKind { DontCare, SizeT, PtrdiffT };
0303 TypeKind TK = TypeKind::DontCare;
0304
0305 public:
0306 ArgType(Kind K = UnknownTy, const char *N = nullptr) : K(K), Name(N) {}
0307 ArgType(QualType T, const char *N = nullptr) : K(SpecificTy), T(T), Name(N) {}
0308 ArgType(CanQualType T) : K(SpecificTy), T(T) {}
0309
0310 static ArgType Invalid() { return ArgType(InvalidTy); }
0311 bool isValid() const { return K != InvalidTy; }
0312
0313 bool isSizeT() const { return TK == TypeKind::SizeT; }
0314
0315 bool isPtrdiffT() const { return TK == TypeKind::PtrdiffT; }
0316
0317
0318 static ArgType PtrTo(const ArgType& A) {
0319 assert(A.K >= InvalidTy && "ArgType cannot be pointer to invalid/unknown");
0320 ArgType Res = A;
0321 Res.Ptr = true;
0322 return Res;
0323 }
0324
0325
0326 static ArgType makeSizeT(const ArgType &A) {
0327 ArgType Res = A;
0328 Res.TK = TypeKind::SizeT;
0329 return Res;
0330 }
0331
0332
0333
0334 static ArgType makePtrdiffT(const ArgType &A) {
0335 ArgType Res = A;
0336 Res.TK = TypeKind::PtrdiffT;
0337 return Res;
0338 }
0339
0340 MatchKind matchesType(ASTContext &C, QualType argTy) const;
0341
0342 QualType getRepresentativeType(ASTContext &C) const;
0343
0344 ArgType makeVectorType(ASTContext &C, unsigned NumElts) const;
0345
0346 std::string getRepresentativeTypeName(ASTContext &C) const;
0347 };
0348
0349 class OptionalAmount {
0350 public:
0351 enum HowSpecified { NotSpecified, Constant, Arg, Invalid };
0352
0353 OptionalAmount(HowSpecified howSpecified,
0354 unsigned amount,
0355 const char *amountStart,
0356 unsigned amountLength,
0357 bool usesPositionalArg)
0358 : start(amountStart), length(amountLength), hs(howSpecified), amt(amount),
0359 UsesPositionalArg(usesPositionalArg), UsesDotPrefix(false) {}
0360
0361 OptionalAmount(bool valid = true)
0362 : start(nullptr),length(0), hs(valid ? NotSpecified : Invalid), amt(0),
0363 UsesPositionalArg(false), UsesDotPrefix(false) {}
0364
0365 explicit OptionalAmount(unsigned Amount)
0366 : start(nullptr), length(0), hs(Constant), amt(Amount),
0367 UsesPositionalArg(false), UsesDotPrefix(false) {}
0368
0369 bool isInvalid() const {
0370 return hs == Invalid;
0371 }
0372
0373 HowSpecified getHowSpecified() const { return hs; }
0374 void setHowSpecified(HowSpecified h) { hs = h; }
0375
0376 bool hasDataArgument() const { return hs == Arg; }
0377
0378 unsigned getArgIndex() const {
0379 assert(hasDataArgument());
0380 return amt;
0381 }
0382
0383 unsigned getConstantAmount() const {
0384 assert(hs == Constant);
0385 return amt;
0386 }
0387
0388 const char *getStart() const {
0389
0390 return start - UsesDotPrefix;
0391 }
0392
0393 unsigned getConstantLength() const {
0394 assert(hs == Constant);
0395 return length + UsesDotPrefix;
0396 }
0397
0398 ArgType getArgType(ASTContext &Ctx) const;
0399
0400 void toString(raw_ostream &os) const;
0401
0402 bool usesPositionalArg() const { return (bool) UsesPositionalArg; }
0403 unsigned getPositionalArgIndex() const {
0404 assert(hasDataArgument());
0405 return amt + 1;
0406 }
0407
0408 bool usesDotPrefix() const { return UsesDotPrefix; }
0409 void setUsesDotPrefix() { UsesDotPrefix = true; }
0410
0411 private:
0412 const char *start;
0413 unsigned length;
0414 HowSpecified hs;
0415 unsigned amt;
0416 bool UsesPositionalArg : 1;
0417 bool UsesDotPrefix;
0418 };
0419
0420
0421 class FormatSpecifier {
0422 protected:
0423 LengthModifier LM;
0424 OptionalAmount FieldWidth;
0425 ConversionSpecifier CS;
0426 OptionalAmount VectorNumElts;
0427
0428
0429
0430
0431 bool UsesPositionalArg;
0432 unsigned argIndex;
0433 public:
0434 FormatSpecifier(bool isPrintf)
0435 : CS(isPrintf), VectorNumElts(false),
0436 UsesPositionalArg(false), argIndex(0) {}
0437
0438 void setLengthModifier(LengthModifier lm) {
0439 LM = lm;
0440 }
0441
0442 void setUsesPositionalArg() { UsesPositionalArg = true; }
0443
0444 void setArgIndex(unsigned i) {
0445 argIndex = i;
0446 }
0447
0448 unsigned getArgIndex() const {
0449 return argIndex;
0450 }
0451
0452 unsigned getPositionalArgIndex() const {
0453 return argIndex + 1;
0454 }
0455
0456 const LengthModifier &getLengthModifier() const {
0457 return LM;
0458 }
0459
0460 const OptionalAmount &getFieldWidth() const {
0461 return FieldWidth;
0462 }
0463
0464 void setVectorNumElts(const OptionalAmount &Amt) {
0465 VectorNumElts = Amt;
0466 }
0467
0468 const OptionalAmount &getVectorNumElts() const {
0469 return VectorNumElts;
0470 }
0471
0472 void setFieldWidth(const OptionalAmount &Amt) {
0473 FieldWidth = Amt;
0474 }
0475
0476 bool usesPositionalArg() const { return UsesPositionalArg; }
0477
0478 bool hasValidLengthModifier(const TargetInfo &Target,
0479 const LangOptions &LO) const;
0480
0481 bool hasStandardLengthModifier() const;
0482
0483 std::optional<LengthModifier> getCorrectedLengthModifier() const;
0484
0485 bool hasStandardConversionSpecifier(const LangOptions &LangOpt) const;
0486
0487 bool hasStandardLengthConversionCombination() const;
0488
0489
0490
0491 static bool namedTypeToLengthModifier(QualType QT, LengthModifier &LM);
0492 };
0493
0494 }
0495
0496
0497
0498
0499 namespace analyze_printf {
0500
0501 class PrintfConversionSpecifier :
0502 public analyze_format_string::ConversionSpecifier {
0503 public:
0504 PrintfConversionSpecifier()
0505 : ConversionSpecifier(true, nullptr, InvalidSpecifier) {}
0506
0507 PrintfConversionSpecifier(const char *pos, Kind k)
0508 : ConversionSpecifier(true, pos, k) {}
0509
0510 bool isObjCArg() const { return kind >= ObjCBeg && kind <= ObjCEnd; }
0511 bool isDoubleArg() const { return kind >= DoubleArgBeg &&
0512 kind <= DoubleArgEnd; }
0513
0514 static bool classof(const analyze_format_string::ConversionSpecifier *CS) {
0515 return CS->isPrintfKind();
0516 }
0517 };
0518
0519 using analyze_format_string::ArgType;
0520 using analyze_format_string::LengthModifier;
0521 using analyze_format_string::OptionalAmount;
0522 using analyze_format_string::OptionalFlag;
0523
0524 class PrintfSpecifier : public analyze_format_string::FormatSpecifier {
0525 OptionalFlag HasThousandsGrouping;
0526 OptionalFlag IsLeftJustified;
0527 OptionalFlag HasPlusPrefix;
0528 OptionalFlag HasSpacePrefix;
0529 OptionalFlag HasAlternativeForm;
0530 OptionalFlag HasLeadingZeroes;
0531 OptionalFlag HasObjCTechnicalTerm;
0532 OptionalFlag IsPrivate;
0533 OptionalFlag IsPublic;
0534 OptionalFlag IsSensitive;
0535 OptionalAmount Precision;
0536 StringRef MaskType;
0537
0538 ArgType getScalarArgType(ASTContext &Ctx, bool IsObjCLiteral) const;
0539
0540 public:
0541 PrintfSpecifier()
0542 : FormatSpecifier( true), HasThousandsGrouping("'"),
0543 IsLeftJustified("-"), HasPlusPrefix("+"), HasSpacePrefix(" "),
0544 HasAlternativeForm("#"), HasLeadingZeroes("0"),
0545 HasObjCTechnicalTerm("tt"), IsPrivate("private"), IsPublic("public"),
0546 IsSensitive("sensitive") {}
0547
0548 static PrintfSpecifier Parse(const char *beg, const char *end);
0549
0550
0551 void setConversionSpecifier(const PrintfConversionSpecifier &cs) {
0552 CS = cs;
0553 }
0554 void setHasThousandsGrouping(const char *position) {
0555 HasThousandsGrouping.setPosition(position);
0556 }
0557 void setIsLeftJustified(const char *position) {
0558 IsLeftJustified.setPosition(position);
0559 }
0560 void setHasPlusPrefix(const char *position) {
0561 HasPlusPrefix.setPosition(position);
0562 }
0563 void setHasSpacePrefix(const char *position) {
0564 HasSpacePrefix.setPosition(position);
0565 }
0566 void setHasAlternativeForm(const char *position) {
0567 HasAlternativeForm.setPosition(position);
0568 }
0569 void setHasLeadingZeros(const char *position) {
0570 HasLeadingZeroes.setPosition(position);
0571 }
0572 void setHasObjCTechnicalTerm(const char *position) {
0573 HasObjCTechnicalTerm.setPosition(position);
0574 }
0575 void setIsPrivate(const char *position) { IsPrivate.setPosition(position); }
0576 void setIsPublic(const char *position) { IsPublic.setPosition(position); }
0577 void setIsSensitive(const char *position) {
0578 IsSensitive.setPosition(position);
0579 }
0580 void setUsesPositionalArg() { UsesPositionalArg = true; }
0581
0582
0583
0584 const PrintfConversionSpecifier &getConversionSpecifier() const {
0585 return cast<PrintfConversionSpecifier>(CS);
0586 }
0587
0588 void setPrecision(const OptionalAmount &Amt) {
0589 Precision = Amt;
0590 Precision.setUsesDotPrefix();
0591 }
0592
0593 const OptionalAmount &getPrecision() const {
0594 return Precision;
0595 }
0596
0597 bool consumesDataArgument() const {
0598 return getConversionSpecifier().consumesDataArgument();
0599 }
0600
0601
0602
0603
0604
0605
0606 ArgType getArgType(ASTContext &Ctx, bool IsObjCLiteral) const;
0607
0608 const OptionalFlag &hasThousandsGrouping() const {
0609 return HasThousandsGrouping;
0610 }
0611 const OptionalFlag &isLeftJustified() const { return IsLeftJustified; }
0612 const OptionalFlag &hasPlusPrefix() const { return HasPlusPrefix; }
0613 const OptionalFlag &hasAlternativeForm() const { return HasAlternativeForm; }
0614 const OptionalFlag &hasLeadingZeros() const { return HasLeadingZeroes; }
0615 const OptionalFlag &hasSpacePrefix() const { return HasSpacePrefix; }
0616 const OptionalFlag &hasObjCTechnicalTerm() const { return HasObjCTechnicalTerm; }
0617 const OptionalFlag &isPrivate() const { return IsPrivate; }
0618 const OptionalFlag &isPublic() const { return IsPublic; }
0619 const OptionalFlag &isSensitive() const { return IsSensitive; }
0620 bool usesPositionalArg() const { return UsesPositionalArg; }
0621
0622 StringRef getMaskType() const { return MaskType; }
0623 void setMaskType(StringRef S) { MaskType = S; }
0624
0625
0626
0627
0628 bool fixType(QualType QT, const LangOptions &LangOpt, ASTContext &Ctx,
0629 bool IsObjCLiteral);
0630
0631 void toString(raw_ostream &os) const;
0632
0633
0634 bool hasValidPlusPrefix() const;
0635 bool hasValidAlternativeForm() const;
0636 bool hasValidLeadingZeros() const;
0637 bool hasValidSpacePrefix() const;
0638 bool hasValidLeftJustified() const;
0639 bool hasValidThousandsGroupingPrefix() const;
0640
0641 bool hasValidPrecision() const;
0642 bool hasValidFieldWidth() const;
0643 };
0644 }
0645
0646
0647
0648
0649 namespace analyze_scanf {
0650
0651 class ScanfConversionSpecifier :
0652 public analyze_format_string::ConversionSpecifier {
0653 public:
0654 ScanfConversionSpecifier()
0655 : ConversionSpecifier(false, nullptr, InvalidSpecifier) {}
0656
0657 ScanfConversionSpecifier(const char *pos, Kind k)
0658 : ConversionSpecifier(false, pos, k) {}
0659
0660 static bool classof(const analyze_format_string::ConversionSpecifier *CS) {
0661 return !CS->isPrintfKind();
0662 }
0663 };
0664
0665 using analyze_format_string::ArgType;
0666 using analyze_format_string::LengthModifier;
0667 using analyze_format_string::OptionalAmount;
0668 using analyze_format_string::OptionalFlag;
0669
0670 class ScanfSpecifier : public analyze_format_string::FormatSpecifier {
0671 OptionalFlag SuppressAssignment;
0672 public:
0673 ScanfSpecifier() :
0674 FormatSpecifier( false),
0675 SuppressAssignment("*") {}
0676
0677 void setSuppressAssignment(const char *position) {
0678 SuppressAssignment.setPosition(position);
0679 }
0680
0681 const OptionalFlag &getSuppressAssignment() const {
0682 return SuppressAssignment;
0683 }
0684
0685 void setConversionSpecifier(const ScanfConversionSpecifier &cs) {
0686 CS = cs;
0687 }
0688
0689 const ScanfConversionSpecifier &getConversionSpecifier() const {
0690 return cast<ScanfConversionSpecifier>(CS);
0691 }
0692
0693 bool consumesDataArgument() const {
0694 return CS.consumesDataArgument() && !SuppressAssignment;
0695 }
0696
0697 ArgType getArgType(ASTContext &Ctx) const;
0698
0699 bool fixType(QualType QT, QualType RawQT, const LangOptions &LangOpt,
0700 ASTContext &Ctx);
0701
0702 void toString(raw_ostream &os) const;
0703
0704 static ScanfSpecifier Parse(const char *beg, const char *end);
0705 };
0706
0707 }
0708
0709
0710
0711
0712 namespace analyze_format_string {
0713
0714 enum PositionContext { FieldWidthPos = 0, PrecisionPos = 1 };
0715
0716 class FormatStringHandler {
0717 public:
0718 FormatStringHandler() {}
0719 virtual ~FormatStringHandler();
0720
0721 virtual void HandleNullChar(const char *nullCharacter) {}
0722
0723 virtual void HandlePosition(const char *startPos, unsigned posLen) {}
0724
0725 virtual void HandleInvalidPosition(const char *startPos, unsigned posLen,
0726 PositionContext p) {}
0727
0728 virtual void HandleZeroPosition(const char *startPos, unsigned posLen) {}
0729
0730 virtual void HandleIncompleteSpecifier(const char *startSpecifier,
0731 unsigned specifierLen) {}
0732
0733 virtual void HandleEmptyObjCModifierFlag(const char *startFlags,
0734 unsigned flagsLen) {}
0735
0736 virtual void HandleInvalidObjCModifierFlag(const char *startFlag,
0737 unsigned flagLen) {}
0738
0739 virtual void HandleObjCFlagsWithNonObjCConversion(const char *flagsStart,
0740 const char *flagsEnd,
0741 const char *conversionPosition) {}
0742
0743
0744 virtual bool HandleInvalidPrintfConversionSpecifier(
0745 const analyze_printf::PrintfSpecifier &FS,
0746 const char *startSpecifier,
0747 unsigned specifierLen) {
0748 return true;
0749 }
0750
0751 virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
0752 const char *startSpecifier,
0753 unsigned specifierLen,
0754 const TargetInfo &Target) {
0755 return true;
0756 }
0757
0758
0759 virtual void handleInvalidMaskType(StringRef MaskType) {}
0760
0761
0762
0763 virtual bool HandleInvalidScanfConversionSpecifier(
0764 const analyze_scanf::ScanfSpecifier &FS,
0765 const char *startSpecifier,
0766 unsigned specifierLen) {
0767 return true;
0768 }
0769
0770 virtual bool HandleScanfSpecifier(const analyze_scanf::ScanfSpecifier &FS,
0771 const char *startSpecifier,
0772 unsigned specifierLen) {
0773 return true;
0774 }
0775
0776 virtual void HandleIncompleteScanList(const char *start, const char *end) {}
0777 };
0778
0779 bool ParsePrintfString(FormatStringHandler &H,
0780 const char *beg, const char *end, const LangOptions &LO,
0781 const TargetInfo &Target, bool isFreeBSDKPrintf);
0782
0783 bool ParseFormatStringHasSArg(const char *beg, const char *end,
0784 const LangOptions &LO, const TargetInfo &Target);
0785
0786 bool ParseScanfString(FormatStringHandler &H,
0787 const char *beg, const char *end, const LangOptions &LO,
0788 const TargetInfo &Target);
0789
0790
0791 bool parseFormatStringHasFormattingSpecifiers(const char *Begin,
0792 const char *End,
0793 const LangOptions &LO,
0794 const TargetInfo &Target);
0795
0796 }
0797 }
0798 #endif