Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Comment: | TSynLog and TSynMapFile now working all together as expected:
|
---|---|
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
d65d00941685dd5ac704fd6e3e69d7e9 |
User & Date: | ab 2011-04-05 18:37:55 |
2011-04-05
| ||
20:11 | fixed compilation for Delphi 2009/2010/XE (incoherent .map file generation) check-in: 614cb71122 user: ab tags: trunk | |
18:37 |
TSynLog and TSynMapFile now working all together as expected:
| |
2011-04-03
| ||
09:00 | TDynArrayHashed now working as expected, with some full regression tests - resulting speed sounds very good check-in: 8195c840e1 user: ab tags: trunk | |
Changes to SynBigTable.pas.
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 .... 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 .... 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 .... 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 .... 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 .... 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 |
St: TStream; tmp: TIntegerDynArray; ok: boolean; begin result := false; SetLength(Deleted,Random(100)+10); fillchar(Deleted[0],length(Deleted)*4,0); start('Deleting '+IntToStr(length(Deleted))+' elements'); for i := 0 to high(Deleted) do repeat repeat id := Random(n)+1; until (T.IDToIndex(id)>=0) and (IntegerScanIndex(pointer(Deleted),length(Deleted),id)<0); // no dup Deleted[i] := id; ................................................................................ for i := 1 to n do if T.Get(i,Data) and not TestString(i,Data) then exit else if withString then if TS.Get(Int32ToUTF8(i),Data) and not TestString(i,Data) then exit; nu := Random(100)+10; start('Updating '+IntToStr(nu)+' elements'); for i := 0 to nu-1 do repeat repeat id := Random(n)+1; until (T.IDToIndex(id)>=0) and (FastFindIntegerSorted(pointer(Updated),UpdatedCount-1,id)<0); // no dup AddSortedInteger(Updated,UpdatedCount,id); ................................................................................ // RandSeed := 1000; start(''); Finalize(By8); n := Random(50)*50+1500; UpdatedCount := 0; FN := ChangeFileExt(paramstr(0),'.syn'); DeleteFile(FN); start('Creating a TSynBigTable with '+IntToStr(n)+' elements'); T := TSynBigTable.Create(FN); if true then try for i := 1 to n do if T.Add(TTestBigTable.CreateString(i))<>i then exit else if T.CurrentInMemoryDataSize>10 shl 20 then // write on disk every 10 MB ................................................................................ start(''); dec(n,1000); FN := FN+'2'; UpdatedCount := 0; DeleteFile(FN); TS := TSynBigTableString.Create(FN); try start('Creating a GUID-indexed TSynBigTableString with '+IntToStr(Length(FileN))+' * 4MB elements'); for i := 0 to high(FileN) do repeat // avoid duplicates (Add=0 if FileN[i] was already there) guid.D1 := Random(maxInt); guid.D2 := Random(65535); guid.D3 := Random(65535); Int64(guid.D4) := Int64(Random(maxInt))*Random(maxInt); FileN[i] := RawUTF8(GuidToString(guid)); ................................................................................ if not TS.Get(FileN[id],data) or not TestString(777,Data) then exit; st := stats(T); start('Clear'); TS.Clear; UpdatedCount := 0; start(st+#13#10#10'Creating a string-indexed TSynBigTableString with '+ IntToStr(n)+' elements'); for i := 1 to n do if TS.Add(TTestBigTable.CreateString(i),Int32ToUTF8(i))<>i then exit else if TS.CurrentInMemoryDataSize>10 shl 20 then // write on disk every 10 MB TS.UpdateToFile; if TS.Count<>n then exit; ................................................................................ WriteBuffer.WriteVarUInt32Array(fDeleted,fDeletedCount,wkSorted); WriteBuffer.WriteVarUInt32Array(fAliasSource,fAliasCount,wkSorted); WriteBuffer.WriteVarUInt32Array(fAliasReal,fAliasCount,wkVarUInt32); UseOffset64 := pointer(fOffset64)<>nil; WriteBuffer.Write(@UseOffset64,1); if UseOffset64 then WriteBuffer.WriteVarUInt64DynArray(fOffset64,fCount,true) else WriteBuffer.WriteVarUInt32Array(fOffset32,fCount,wkOffset); Offs := WriteBuffer.TotalWritten-Offs+8; //assert(Offs<maxInt); WriteBuffer.Write(@Offs,4); WriteBuffer.Write(@magic,4); WriteBuffer.Flush; SetEndOfFile(fFile); // always force truncate at end of header if forceFlushOnDisk then |
| | | | | | |
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 .... 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 .... 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 .... 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 .... 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 .... 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 |
St: TStream; tmp: TIntegerDynArray; ok: boolean; begin result := false; SetLength(Deleted,Random(100)+10); fillchar(Deleted[0],length(Deleted)*4,0); start('Deleting '+IntToString(length(Deleted))+' elements'); for i := 0 to high(Deleted) do repeat repeat id := Random(n)+1; until (T.IDToIndex(id)>=0) and (IntegerScanIndex(pointer(Deleted),length(Deleted),id)<0); // no dup Deleted[i] := id; ................................................................................ for i := 1 to n do if T.Get(i,Data) and not TestString(i,Data) then exit else if withString then if TS.Get(Int32ToUTF8(i),Data) and not TestString(i,Data) then exit; nu := Random(100)+10; start('Updating '+IntToString(nu)+' elements'); for i := 0 to nu-1 do repeat repeat id := Random(n)+1; until (T.IDToIndex(id)>=0) and (FastFindIntegerSorted(pointer(Updated),UpdatedCount-1,id)<0); // no dup AddSortedInteger(Updated,UpdatedCount,id); ................................................................................ // RandSeed := 1000; start(''); Finalize(By8); n := Random(50)*50+1500; UpdatedCount := 0; FN := ChangeFileExt(paramstr(0),'.syn'); DeleteFile(FN); start('Creating a TSynBigTable with '+IntToString(n)+' elements'); T := TSynBigTable.Create(FN); if true then try for i := 1 to n do if T.Add(TTestBigTable.CreateString(i))<>i then exit else if T.CurrentInMemoryDataSize>10 shl 20 then // write on disk every 10 MB ................................................................................ start(''); dec(n,1000); FN := FN+'2'; UpdatedCount := 0; DeleteFile(FN); TS := TSynBigTableString.Create(FN); try start('Creating a GUID-indexed TSynBigTableString with '+IntToString(Length(FileN))+' * 4MB elements'); for i := 0 to high(FileN) do repeat // avoid duplicates (Add=0 if FileN[i] was already there) guid.D1 := Random(maxInt); guid.D2 := Random(65535); guid.D3 := Random(65535); Int64(guid.D4) := Int64(Random(maxInt))*Random(maxInt); FileN[i] := RawUTF8(GuidToString(guid)); ................................................................................ if not TS.Get(FileN[id],data) or not TestString(777,Data) then exit; st := stats(T); start('Clear'); TS.Clear; UpdatedCount := 0; start(st+#13#10#10'Creating a string-indexed TSynBigTableString with '+ IntToString(n)+' elements'); for i := 1 to n do if TS.Add(TTestBigTable.CreateString(i),Int32ToUTF8(i))<>i then exit else if TS.CurrentInMemoryDataSize>10 shl 20 then // write on disk every 10 MB TS.UpdateToFile; if TS.Count<>n then exit; ................................................................................ WriteBuffer.WriteVarUInt32Array(fDeleted,fDeletedCount,wkSorted); WriteBuffer.WriteVarUInt32Array(fAliasSource,fAliasCount,wkSorted); WriteBuffer.WriteVarUInt32Array(fAliasReal,fAliasCount,wkVarUInt32); UseOffset64 := pointer(fOffset64)<>nil; WriteBuffer.Write(@UseOffset64,1); if UseOffset64 then WriteBuffer.WriteVarUInt64DynArray(fOffset64,fCount,true) else WriteBuffer.WriteVarUInt32Array(fOffset32,fCount,wkOffsetU); Offs := WriteBuffer.TotalWritten-Offs+8; //assert(Offs<maxInt); WriteBuffer.Write(@Offs,4); WriteBuffer.Write(@magic,4); WriteBuffer.Flush; SetEndOfFile(fFile); // always force truncate at end of header if forceFlushOnDisk then |
Changes to SynCommons.pas.
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 ... 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 ... 649 650 651 652 653 654 655 656 657 658 659 660 661 662 .... 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 .... 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 .... 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 .... 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 .... 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 .... 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 .... 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 .... 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 .... 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 .... 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 .... 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 .... 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 .... 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 .... 5784 5785 5786 5787 5788 5789 5790 5791 5792 5793 5794 5795 5796 5797 5798 5799 5800 5801 5802 5803 5804 5805 5806 5807 5808 5809 5810 5811 5812 5813 5814 5815 5816 5817 5818 5819 5820 5821 5822 5823 5824 5825 5826 5827 5828 5829 5830 5831 5832 .... 5883 5884 5885 5886 5887 5888 5889 5890 5891 5892 5893 5894 5895 5896 5897 .... 6530 6531 6532 6533 6534 6535 6536 6537 6538 6539 6540 6541 6542 6543 .... 8272 8273 8274 8275 8276 8277 8278 8279 8280 8281 8282 8283 8284 8285 .... 9234 9235 9236 9237 9238 9239 9240 9241 9242 9243 9244 9245 9246 9247 9248 .... 9255 9256 9257 9258 9259 9260 9261 9262 9263 9264 9265 9266 9267 9268 9269 ..... 11821 11822 11823 11824 11825 11826 11827 11828 11829 11830 11831 11832 11833 11834 11835 11836 11837 11838 11839 11840 11841 11842 11843 11844 11845 11846 11847 11848 11849 11850 11851 11852 11853 11854 11855 11856 11857 11858 11859 11860 ..... 12141 12142 12143 12144 12145 12146 12147 12148 12149 12150 12151 12152 12153 12154 12155 12156 12157 12158 12159 12160 12161 12162 12163 12164 12165 12166 12167 12168 ..... 12958 12959 12960 12961 12962 12963 12964 12965 12966 12967 12968 12969 12970 12971 12972 12973 ..... 13696 13697 13698 13699 13700 13701 13702 13703 13704 13705 13706 13707 13708 13709 ..... 14772 14773 14774 14775 14776 14777 14778 14779 14780 14781 14782 14783 14784 14785 ..... 14789 14790 14791 14792 14793 14794 14795 14796 14797 14798 14799 14800 14801 14802 14803 14804 14805 14806 14807 14808 14809 14810 14811 14812 14813 14814 14815 14816 14817 14818 14819 14820 14821 14822 ..... 14829 14830 14831 14832 14833 14834 14835 14836 14837 14838 14839 14840 14841 14842 ..... 14872 14873 14874 14875 14876 14877 14878 14879 14880 14881 14882 14883 14884 14885 14886 ..... 14889 14890 14891 14892 14893 14894 14895 14896 14897 14898 14899 14900 14901 14902 14903 ..... 14907 14908 14909 14910 14911 14912 14913 14914 14915 14916 14917 14918 14919 14920 ..... 16930 16931 16932 16933 16934 16935 16936 16937 16938 16939 16940 16941 16942 16943 16944 16945 ..... 17456 17457 17458 17459 17460 17461 17462 17463 17464 17465 17466 17467 17468 17469 17470 17471 17472 17473 17474 17475 17476 17477 17478 17479 17480 17481 17482 17483 17484 17485 ..... 17513 17514 17515 17516 17517 17518 17519 17520 17521 17522 17523 17524 17525 17526 17527 17528 ..... 17741 17742 17743 17744 17745 17746 17747 17748 17749 17750 17751 17752 17753 17754 17755 17756 ..... 18048 18049 18050 18051 18052 18053 18054 18055 18056 18057 18058 18059 18060 18061 18062 18063 18064 18065 18066 18067 18068 18069 18070 18071 18072 18073 18074 18075 18076 18077 18078 18079 ..... 18268 18269 18270 18271 18272 18273 18274 18275 18276 18277 18278 18279 18280 18281 ..... 19289 19290 19291 19292 19293 19294 19295 19296 19297 19298 19299 19300 19301 19302 19303 ..... 19325 19326 19327 19328 19329 19330 19331 19332 19333 19334 19335 19336 19337 19338 19339 ..... 19346 19347 19348 19349 19350 19351 19352 19353 19354 19355 19356 19357 19358 19359 19360 19361 19362 19363 19364 19365 19366 ..... 19471 19472 19473 19474 19475 19476 19477 19478 19479 19480 19481 19482 19483 19484 ..... 19499 19500 19501 19502 19503 19504 19505 19506 19507 19508 19509 19510 19511 19512 19513 ..... 19538 19539 19540 19541 19542 19543 19544 19545 19546 19547 19548 19549 19550 19551 19552 ..... 19563 19564 19565 19566 19567 19568 19569 19570 19571 19572 19573 19574 19575 19576 19577 ..... 19584 19585 19586 19587 19588 19589 19590 19591 19592 19593 19594 19595 19596 19597 19598 19599 19600 19601 19602 19603 19604 19605 19606 19607 19608 19609 19610 19611 ..... 19698 19699 19700 19701 19702 19703 19704 19705 19706 19707 19708 19709 19710 19711 19712 ..... 19730 19731 19732 19733 19734 19735 19736 19737 19738 19739 19740 19741 19742 19743 19744 19745 19746 ..... 19800 19801 19802 19803 19804 19805 19806 19807 19808 19809 19810 19811 19812 19813 19814 ..... 19866 19867 19868 19869 19870 19871 19872 19873 19874 19875 19876 19877 19878 19879 19880 19881 19882 19883 19884 19885 19886 19887 19888 19889 19890 19891 19892 ..... 20605 20606 20607 20608 20609 20610 20611 20612 20613 20614 20615 20616 20617 20618 20619 ..... 21288 21289 21290 21291 21292 21293 21294 21295 21296 21297 21298 21299 21300 21301 21302 ..... 22110 22111 22112 22113 22114 22115 22116 22117 22118 22119 22120 22121 22122 22123 22124 22125 22126 22127 22128 22129 22130 22131 22132 22133 22134 22135 22136 22137 22138 22139 22140 ..... 22146 22147 22148 22149 22150 22151 22152 22153 22154 22155 22156 22157 22158 22159 22160 22161 22162 22163 22164 22165 22166 22167 22168 22169 22170 22171 22172 22173 22174 22175 22176 22177 22178 22179 22180 22181 22182 22183 22184 22185 22186 22187 22188 22189 22190 22191 22192 22193 22194 22195 22196 22197 22198 22199 22200 22201 22202 22203 22204 22205 22206 22207 22208 22209 22210 22211 22212 22213 22214 22215 22216 22217 22218 22219 22220 22221 22222 22223 22224 22225 22226 22227 22228 22229 22230 22231 22232 22233 22234 22235 22236 22237 22238 22239 22240 22241 22242 22243 22244 22245 22246 22247 22248 22249 22250 22251 22252 22253 22254 22255 22256 ..... 22266 22267 22268 22269 22270 22271 22272 22273 22274 22275 22276 22277 22278 22279 22280 22281 22282 22283 22284 22285 22286 22287 22288 22289 22290 22291 22292 22293 22294 22295 22296 22297 22298 22299 22300 22301 22302 22303 22304 22305 22306 22307 22308 22309 22310 22311 22312 22313 22314 ..... 22315 22316 22317 22318 22319 22320 22321 22322 22323 22324 22325 22326 22327 22328 22329 ..... 22334 22335 22336 22337 22338 22339 22340 22341 22342 22343 22344 22345 22346 22347 22348 22349 ..... 22386 22387 22388 22389 22390 22391 22392 22393 22394 22395 22396 22397 22398 22399 22400 22401 22402 22403 22404 22405 22406 22407 22408 22409 22410 22411 22412 22413 22414 22415 22416 22417 22418 22419 ..... 22421 22422 22423 22424 22425 22426 22427 22428 22429 22430 22431 22432 22433 22434 ..... 22496 22497 22498 22499 22500 22501 22502 22503 22504 22505 22506 22507 22508 22509 ..... 22648 22649 22650 22651 22652 22653 22654 22655 22656 22657 22658 22659 22660 22661 22662 ..... 22689 22690 22691 22692 22693 22694 22695 22696 22697 22698 22699 22700 22701 22702 22703 |
TSynValidateTableUniqueField instance is created if tfoUnique is in Options) - dedicated TSynTableFieldProperties.Filter method for filtering (using common TSynFilter classes, working at UTF-8 Text content) - faster implementation of Move() for Delphi versions with no FastCode inside - new ConvertCaseUTF8(), UpperCaseU(), LowerCaseU(), Int64ToUInt32(), GetCardinalDef(), IsValidEmail, IsValidIP4Address(), PatchCodePtrUInt(), GetCaptionFromClass(), GetDisplayNameFromClass(), DateTimeToIso8601Text() StrUInt32(), StringBufferToUtf8(), IsZero(), AddPrefixToCSV() procedures or functions (with associated tests) - new grep-like IsMatch() function for basic pattern matching - new BinToBase64, Base64ToBin and IsBase64 *fast* conversion functions (with optimized assembler version, using CPU pipelining and lookup table) - introducing the GarbageCollector TObjectList for handling a global garbage collector for instances which must live during the whole executable process (used e.g. to avoid a memory leak for "class var" or such variables) - great performance improvement in TSynTableFieldProperties for update process - now TTextWriter can have a custom internal buffer size (default 1024 bytes) - fixed issue in TSynTable.Data() method: ID was not set as expected - fixed issue in TSynTableFieldProperties: wrong constraint evaluation and index refresh at records update } ................................................................................ function UTF8ToWideString(const Text: RawUTF8): WideString; /// convert any UTF-8 encoded String into a generic SynUnicode Text function UTF8ToSynUnicode(const Text: RawUTF8): SynUnicode; /// convert any Ansi 7 bit encoded String into a generic VCL Text // - the Text content must contain only 7 bit pure ASCII characters function Ansi7ToString(const Text: RawByteString): string; /// convert any generic VCL Text into Ansi 7 bit encoded String // - the Text content must contain only 7 bit pure ASCII characters function StringToAnsi7(const Text: string): RawByteString; /// fast Format() function replacement, optimized for RawUTF8 // - only supported token is %, which works only for integer and string type of ................................................................................ {$ifndef PURE_PASCAL} {$ifndef ISDELPHI2007ANDUP} /// faster implementation of Move() for Delphi versions with no FastCode inside procedure Move(const Source; var Dest; Count: Integer); {$endif} {$endif} {$ifndef FPC} { these asm function use some low-level system.pas calls } /// use our fast asm RawUTF8 version of Trim() function Trim(const S: RawUTF8): RawUTF8; /// use our fast asm version of CompareMem() ................................................................................ // - returns the index of the added element in the dynamic array // - note that because of dynamic array internal memory managment, adding // will be a bit slower than e.g. with a TList: the list is reallocated // every time a record is added - but in practice, with FastMM4 or // SynScaleMM, there is no big speed penalty - for even better speed, you // can also specify an external count variable in Init(...,@Count) method function Add(const Elem): integer; /// add elements from a given dynamic array // - the supplied source DynArray MUST be of the same exact type as the // current used for this TDynArray // - you can specify the start index and the number of items to take from // the source dynamic array (leave as -1 to add till the end) procedure AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1); /// add an element to the dynamic array at the position specified by Index ................................................................................ // - wkVarUInt32 will write the content using our 32-bit variable-length integer // encoding // - wkVarInt32 will write the content using our 32-bit variable-length integer // encoding and the by-two complement (0=0,1=1,2=-1,3=2,4=-2...) // - wkSorted will write an increasing array of integers, handling the special // case of a difference of 1 between two values (therefore is very optimized // to store an array of IDs) // - wkOffset will write the difference between two successive values, handling // constant difference in an optimized manner TFileBufferWriterKind = (wkUInt32, wkVarUInt32, wkVarInt32, wkSorted, wkOffset); PFileBufferWriter = ^TFileBufferWriter; /// this structure can be used to speed up writing to a file // - big speed up if data is written in small blocks // - also handle optimized storage of any dynamic array of Integer/Int64/RawUTF8 // - is defined either as an object either as a record, due to a bug ................................................................................ // used by ReadChunk() method fBufTemp: RawByteString; /// get Isize + buffer from current memory map or fBufTemp into (P,PEnd) procedure ReadChunk(var P, PEnd: PByte); public /// initialize the buffer, and specify a file to use for reading procedure Open(aFile: THandle); /// close all internal mapped files // - call Open() again to use the Read() methods procedure Close; {$ifndef CPU64} /// change the current reading position, from the beginning of the file // - returns TRUE if success, or FALSE if Offset is out of range function Seek(Offset: Int64): boolean; overload; ................................................................................ function Read(Data: pointer; DataLen: integer): integer; overload; /// read some UTF-8 encoded text at the current position // - returns the resulting text length, in bytes function Read(out Text: RawUTF8): integer; overload; /// read one byte // - if reached end of file, don't raise any error, but returns 0 function ReadByte: PtrUInt; {$ifdef HASINLINE}inline;{$endif} /// read one cardinal value encoded using our 32-bit variable-length integer function ReadVarUInt32: PtrUInt; /// read one integer value encoded using our 32-bit variable-length integer, // and the by-two complement function ReadVarInt32: PtrInt; /// read one UInt64 value encoded using our 64-bit variable-length integer function ReadVarUInt64: QWord; ................................................................................ // in big-endian order (most-signignifican byte first): use it for display procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: integer); /// fast conversion from a pointer data into hexa chars, ready to be displayed // - use internally BinToHexDisplay() function PointerToHex(aPointer: Pointer): RawUTF8; /// fast conversion from binary data into Base64 encoded text function BinToBase64(const s: RawByteString): RawByteString; overload; /// fast conversion from binary data into Base64 encoded text function BinToBase64(Bin: PAnsiChar; BinBytes: integer): RawByteString; overload; /// fast conversion from binary data into Base64 encoded text ................................................................................ function GetDelphiCompilerVersion: RawUTF8; { ************ Logging classes and functions } type /// the available logging events, as handled by TSynLog // - sllError will log errors // - sllInfo will log general information events // - sllDebug will log detailed debugging information // - sllLastError will log the GetLastError OS message // - sllException will log all exception raised - available since Windows XP // - sllExceptionOS will log all OS low-level exceptions (EDivByZero, ................................................................................ Instance: TObject=nil); overload; /// call this method to add some information to the log at a specified level // - if Instance is set and Text is not '', it will log the corresponding // class name and address (to be used e.g. if you didn't call TSynLog.Enter() // method first) // - if Instance is set and Text is '', will behave the same as // Log(Level,Instance), i.e. write the Instance as JSON content procedure Log(Level: TSynLogInfo; const Text: RawUTF8=''; Instance: TObject=nil); overload; /// call this method to add the content of an object to the log at a // specified level // - TSynLog will write the class and hexa address - TSQLLog will write the // object JSON content procedure Log(Level: TSynLogInfo; Instance: TObject); overload; /// call this method to add the content of most low-level types to the log // at a specified level // - TSynLog will handle enumerations and dynamic array; TSQLLog will be // able to write TObject/TSQLRecord and sets content as JSON procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); overload; /// retrieve the associated logging instance function Instance: TSynLog; end; {{ regroup several logs under an unique family name - you should usualy use one family per application or per architectural module: e.g. a server application may want to log in separate files the ................................................................................ fIdent: integer; fDestinationPath: TFileName; fBufferSize: integer; fHRTimeStamp: boolean; fWithUnitName: boolean; fAutoFlush: cardinal; fHandleExceptions: boolean; function CreateSynLog: TSynLog; procedure SetAutoFlush(TimeOut: cardinal); procedure SetLevel(const aLevel: TSynLogInfos); public /// intialize for a TSynLog class family // - add it in the global SynLogFileFamily[] list constructor Create(aSynLog: TSynLogClass); /// release associated memory destructor Destroy; override; /// retrieve the corresponding log file of this thread and family // - creates the TSynLog if not already existing for this current thread function SynLog: TSynLog; published /// the associated TSynLog class property SynLogClass: TSynLogClass read fSynLogClass; /// index in global SynLogFileFamily[] and threadvar SynLogFileIndex[] lists property Ident: integer read fIdent; /// the current level of logging information for this family property Level: TSynLogInfos read fLevel write SetLevel; ................................................................................ // disk, whatever the current content size is // - by default, the log file will be written for every 4 KB of log - this // will ensure that the main application won't be slow down by logging // - in order not to loose any log, a background thread can be created // and will be responsible of flushing all pending log content every // period of time (e.g. every 10 seconds) property AutoFlushTimeOut: cardinal read fAutoFlush write SetAutoFlush; end; /// used to store the identification of one recursivity level TSynLogCurrentIdent = packed record Instance: TObject; ClassType: TClass; Method: PUTF8Char; end; PSynLogCurrentIdent = ^TSynLogCurrentIdent; /// used to store the identification of all recursivity levels TSynLogCurrentIdents = array[0..maxInt div sizeof(TSynLogCurrentIdent)-1] of TSynLogCurrentIdent; PSynLogCurrentIdents = ^TSynLogCurrentIdents; /// a per-family and/or per-thread log file content // - you should create a sub class per kind of log file // ! TSynLogDB = class(TSynLog); // - the TSynLog instance won't be allocated in heap, but will share a // per-thread (if Family.PerThreadLog=TRUE) or global private log file instance // - was very optimized for speed, if no logging is written, and even during // log write (using an internal TTextWriter) TSynLog = class(TObject, ISynLog) protected fWriter: TTextWriter; fFamily: TSynLogFamily; fWriterStream: TStream; fHeaderWritten: boolean; fLockCS: PRTLCriticalSection; fStartTimeStamp: Int64; fFileName: TFileName; /// set by Enter() method fRecursionCount: integer; fRecursionMax: integer; fRecursion: PSynLogCurrentIdents; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; class function FamilyCreate: TSynLogFamily; procedure DoEnterLeave(aLevel: TSynLogInfo); procedure CreateLogWriter; virtual; procedure LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar; ................................................................................ - if you just need to access the log inside the method block, you may not need any ISynLog interface: ! procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self,'SQLFlush'); ! // do some stuff ! end; - Enter() will write the class name (and the unit name for classes with published properties, if TSynLogFamily.WithUnitName is true) for both enter (+) and leave (-) events: $ 20110325 19325801 + MyDBUnit.TMyDB(004E11F4).SQLExecute $ 20110325 19325801 info SQL=SELECT * FROM Table; $ 20110325 19325801 - MyDBUnit.TMyDB(004E11F4).SQLExecute } class function Enter(aInstance: TObject; aMethodName: PUTF8Char): ISynLog; overload; {{ to be called and assigned to a ISynLog interface at the beginning of a method - this is the main method to be called within a class method: ! class function TMyDB.SQLValidate(const SQL: RawUTF8): boolean; ! var ILog: ISynLog; ! begin ! ILog := TSynLogDB.Enter(self,'SQLValidate'); ................................................................................ - if you just need to access the log inside the method block, you may not need any ISynLog interface: ! class procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self,'SQLFlush'); ! // do some stuff ! end; - Enter() will write the class name (and the unit name for classes with published properties, if TSynLogFamily.WithUnitName is true) for both enter (+) and leave (-) events: $ 20110325 19325801 + MyDBUnit.TMyDB.SQLValidate $ 20110325 19325801 info SQL=SELECT * FROM Table returned 1; $ 20110325 19325801 - MyDBUnit.TMyDB.SQLValidate } class function Enter(aClassType: TClass; aMethodName: PUTF8Char): ISynLog; overload; /// retrieve the current instance of this TSynLog class // - to be used for direct logging, without any Enter/Leave: // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',[SQL]); // - is just a wrapper around Family.SynLog - the same code will work: // ! TSynLogDB.Family.SynLog.Log(llError,'The % statement didn't work',[SQL]); class function Add: TSynLog; {$ifdef HASINLINE}inline;{$endif} ................................................................................ // class name and address (to be used e.g. if you didn't call TSynLog.Enter() // method first) - for instance // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector); // will append this line to the log: // $ 0000000000002DB9 debug TObjectList(00425E68) GarbageCollector // - if Instance is set and Text is '', will behave the same as // Log(Level,Instance), i.e. write the Instance as JSON content procedure Log(Level: TSynLogInfo; const Text: RawUTF8=''; aInstance: TObject=nil); overload; /// call this method to add the content of an object to the log at a // specified level // - this default implementation will just write the class name and its hexa // pointer value, and handle TList, TCollections and TStrings - for instance: // ! TSynLog.Add.Log(sllDebug,GarbageCollector); // will append this line to the log: ................................................................................ /// call this method to add the content of most low-level types to the log // at a specified level // - this overriden implementation will write the value content, // written as human readable JSON: handle dynamic arrays and enumerations // - TSQLLog from SQLite3Commons unit will be able to write // TObject/TSQLRecord and sets content as JSON procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); overload; published /// the associated logging family property GenericFamily: TSynLogFamily read fFamily; /// the associated file name containing the log // - this is accurate only with the default implementation of the class: // any child may override it with a custom logging mechanism property FileName: TFileName read fFileName; ................................................................................ var L2: integer; begin result := ''; // somewhat faster if result is freed before any SetLength() if L=0 then L := StrLen(P); if L=0 then exit; SetLength(result,L); // maximum posible unicode size (if all <#128) L2 := UTF8ToWideChar(pointer(result),P,L) shr 1; if L2<>L then SetLength(result,L2); end; function WinAnsiToUnicodeString(WinAnsi: PAnsiChar; WinAnsiLen: integer): UnicodeString; var i: integer; begin SetLength(result,WinAnsiLen); for i := 0 to WinAnsiLen-1 do PWordArray(result)[i] := WinAnsiTableA[WinAnsi[i]]; // very fast conversion end; {$endif} {$ifdef UNICODE} function Ansi7ToString(const Text: RawByteString): string; var i: integer; begin SetLength(result,length(Text)); for i := 0 to length(Text)-1 do PWordArray(result)[i] := PByteArray(Text)[i]; // no conversion for 7 bit Ansi end; {$else} function Ansi7ToString(const Text: RawByteString): string; begin result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign end; {$endif} {$ifdef UNICODE} function StringToAnsi7(const Text: string): RawByteString; var i: integer; begin SetLength(result,length(Text)); for i := 0 to length(Text)-1 do PByteArray(result)[i] := PWordArray(Text)[i]; // no conversion for 7 bit Ansi end; {$else} function StringToAnsi7(const Text: string): RawByteString; begin result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign ................................................................................ SetString(result,PAnsiChar(pointer(S)),length(S)*2+1); // +1 for last wide #0 end; {$else} function StringToRawUnicode(const S: string): RawUnicode; var i, L: integer; begin L := length(S); SetLength(result,L*2+1); // +1 for last wide #0 if GetACP<>1252 then begin // low-level MBCS RTL function including last widechar #0 SetLength(result,MultiByteToWideChar(GetACP, 0, Pointer(s), L, pointer(result), L)); end else // fast WinAnsi conversion for i := 0 to L do // includes S[L]=#0 -> last widechar #0 PWordArray(result)[i] := WinAnsiTable[PByteArray(S)[i]]; ................................................................................ // this Pos() is seldom used, it was decided to only define it under // Delphi 2009/2010/XE (which expect such a RawUTF8 specific overloaded version) function Pos(const substr, str: RawUTF8): Integer; overload; begin Result := PosEx(substr, str, 1); end; {$endif UNICODE} {$ifndef ISDELPHI2007ANDUP} /// faster implementation of Move() for Delphi versions with no FastCode inside procedure Move(const Source; var Dest; Count: Integer); asm // eax=source edx=dest ecx=count ................................................................................ if byte(U^) and $80<>0 then break; // 7 bits char check only until false; {$endif} // find beginning of next word U := FindNextUTF8WordBegin(U); until U=nil; end; function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: Integer): boolean; var I: Integer; B,C: byte; begin result := false; // return false if any invalid char if Pointer(ConvertHexToBin)=nil then ................................................................................ var F: THandle; Size: integer; begin result := ''; if FileName='' then exit; F := FileOpen(FileName,fmOpenRead or fmShareDenyNone); if F<>THandle(-1) then begin {$ifdef LINUX} Size := FileSeek(F,0,soFromEnd); FileSeek(F,0,soFromBeginning); {$else} Size := GetFileSize(F,nil); {$endif} SetLength(result,Size); ................................................................................ function FileFromString(const Content: RawByteString; const FileName: TFileName; FlushOnDisk: boolean=false): boolean; var F: THandle; L: integer; begin result := false; F := FileCreate(FileName); if F=THandle(-1) then exit; if pointer(Content)<>nil then L := FileWrite(F,pointer(Content)^,length(Content)) else L := 0; result := (L=length(Content)); {$ifdef MSWINDOWS} if FlushOnDisk then ................................................................................ SetString(result,tmp,15); end; end; function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUTF8; // we use YYYYMMDDTdate format begin result := ''; SetLength(result,8+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} DateToIso8601PChar(Date,pointer(result),Expanded); end; /// basic Date conversion into ISO-8601 // - use 'YYYYMMDD' format if not Expanded // - use 'YYYY-MM-DD' format if Expanded function DateToIso8601(Y,M,D: cardinal; Expanded: boolean): RawUTF8; overload; begin result := ''; SetLength(result,8+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} DateToIso8601PChar(pointer(result),Expanded,Y,M,D); end; function TimeToIso8601(Time: TDateTime; Expanded: boolean): RawUTF8; // we use Thhmmss format begin result := ''; SetLength(result,7+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} TimeToIso8601PChar(Time,pointer(result),Expanded); end; function DateTimeToIso8601Text(DT: TDateTime): RawUTF8; ................................................................................ {$ifdef MSWINDOWS} Now: TSystemTime; {$else} D: TDateTime; {$endif} begin if aFileName='' then exit; F := FileOpen(aFileName,fmOpenWrite); if F=THandle(-1) then begin F := FileCreate(aFileName); if F=THandle(-1) then exit; end; // append to end of file if FileSeek(F,0,soFromEnd)>MAXLOGSIZE then begin // rotate log file if too big FileClose(F); Old := aFileName+'.bak'; // '.log.bak' DeleteFile(Old); // rotate once RenameFile(aFileName,Old); F := FileCreate(aFileName); if F=THandle(-1) then exit; end; PWord(@Date)^ := 13+10 shl 8; // first go to next line {$ifdef MSWINDOWS} GetLocalTime(Now); // windows dedicated function DateToIso8601PChar(@Date[3],true,Now.wYear,Now.wMonth,Now.wDay); TimeToIso8601PChar(@Date[13],true,Now.wHour,Now.wMinute,Now.wSecond,' '); ................................................................................ BuildYear := SystemTime.wYear; end; end; finally Freemem(Pt); end; end; Main := SysUtils.IntToStr(Major) + '.' + SysUtils.IntToStr(Minor); Detailed := Main+ '.' + SysUtils.IntToStr(Release) + '.' + SysUtils.IntToStr(Build); if BuildDateTime=0 then // get build date from file age BuildDateTime := FileAgeToDateTime(FileName); end; function TFileVersion.Version32: integer; begin result := Major shl 16+Minor shl 8+Release; ................................................................................ exit; // avoid GPF if void SetCount(result+1); P := pointer(PtrUInt(Value^)+PtrUInt(result)*ElemSize); if ElemType=nil then move(Elem,P^,ElemSize) else CopyArray(P,@Elem,ElemType,1); end; procedure TDynArray.Insert(Index: Integer; const Elem); var n: integer; P: PByteArray; begin if Value=nil then exit; // avoid GPF if void ................................................................................ result := Hasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^*2); end; procedure TDynArrayHashed.Init(aTypeInfo: pointer; var aValue; aHashElement: TDynArrayHashOne=nil; aCompare: TDynArraySortCompare=nil; aHasher: THasher=nil; aCountPointer: PInteger=nil); var FieldTable: PFieldTable; begin inherited Init(aTypeInfo,aValue,aCountPointer); if @aHasher=nil then begin if @DefaultHasher=nil then DefaultHasher := @kr32; fHasher := DefaultHasher; end else ................................................................................ case PByte(ElemType)^ of tkLString: aHashElement := HashAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aHashElement := HashUnicodeString; tkRecord: begin FieldTable := ElemType; inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count>0 then with FieldTable^.Fields[0] do if Offset=0 then case TypeInfo^^.Kind of tkLString: aHashElement := HashAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aHashElement := HashUnicodeString; end; end; end; fHashElement := aHashElement; if @aCompare=nil then begin aCompare := SORTFIRSTFIELD[TypeInfoToKnown(aTypeInfo)]; if @aCompare=nil then // will match default HashOne algo, i.e. hash first field of record if (ElemType<>nil) and (PByte(ElemType)^=tkRecord) then begin FieldTable := ElemType; inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count=0 then case ElemSize of 1: aCompare := @SortDynArrayByte; 2: aCompare := @SortDynArrayWord; 4: aCompare := @SortDynArrayCardinal; 8: aCompare := @SortDynArrayInt64; end else ................................................................................ 8: aCompare := @SortDynArrayInt64; end else case TypeInfo^^.Kind of tkLString: aCompare := @SortDynArrayAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aCompare := @SortDynArrayUnicodeString; end; end; end; fCompare := aCompare; ReHash; end; ................................................................................ end; until false; result := -1; // mark not found end; function TDynArrayHashed.HashOne(const Elem): cardinal; var FieldTable: PFieldTable; label Bin, LStr, WStr; begin if @fHashElement<>nil then result := fHashElement(Elem,fHasher) else if ElemType=nil then goto Bin else case PByte(ElemType)^ of tkLString: ................................................................................ result := fHasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^); tkWString{$ifdef UNICODE}, tkUString{$endif}: WStr: if PtrUInt(Elem)=0 then result := 0 else result := fHasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^*2); tkRecord: begin FieldTable := ElemType; inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count=0 then // only binary content -> hash full content Bin: case ElemSize of 1: result := byte(Elem); 2: result := word(Elem); 4: result := cardinal(Elem); 8: result := Int64Rec(Elem).Lo xor Int64Rec(Elem).Hi; ................................................................................ if Offset<>0 then // hash whole starting binary content of record result := fHasher(0,@Elem,Offset) else // hash first string field of record case TypeInfo^^.Kind of tkLString: goto LStr; tkWString{$ifdef UNICODE}, tkUString{$endif}: goto WStr; else result := 0; // unhandled type end; end; else result := 0; end; if result=0 then result := 1; // never return 0 (indicates a free position in fHashs[]) ................................................................................ fOwner := Owner; end; function TSynTestCase.RandomString(CharCount: Integer): RawByteString; var V: cardinal; P: PAnsiChar; begin Result := ''; SetLength(Result,CharCount); P := pointer(Result); while CharCount>0 do begin if CharCount>5 then begin V := Random(maxInt); // fast: one random compute per 5 chars P[0] := AnsiChar(32+V and 127); V := V shr 7; P[1] := AnsiChar(32+V and 127); V := V shr 7; P[2] := AnsiChar(32+V and 127); V := V shr 7; ................................................................................ Check(Cities[i].Name=''); // ForAddind = not added Cities[i] := City; end; Check(ACities.Count=3); Check(City.Name='Iasi'); Check(ACities.FindHashed(City)>=0); for i := 1 to 2000 do begin City.Name := IntToStr(i); City.Latitude := i*3.14; City.Longitude := i*6.13; Check(ACities.FindHashedAndUpdate(City,true)=i+2,'multiple ReHash'); Check(ACities.FindHashed(City)=i+2); end; ACities.Capacity := CITIES_MAX+3; // make it as fast as possible for i := 2001 to CITIES_MAX do begin City.Name := IntToStr(i); City.Latitude := i*3.14; City.Longitude := i*6.13; Check(ACities.FindHashedAndUpdate(City,true)=i+2,'use Capacity: no ReHash'); Check(ACities.FindHashed(City.Name)=i+2); end; for i := 1 to CITIES_MAX do begin N := IntToStr(i); j := ACities.FindHashed(N); Check(j=i+2,'hashing with string not City.Name'); Check(Cities[j].Name=N); CheckSame(Cities[j].Latitude,i*3.14); CheckSame(Cities[j].Longitude,i*6.13); end; end; ................................................................................ JSON_BASE64_MAGIC_UTF8: RawUTF8; procedure Fill(var F: TFileVersion; i: integer); begin F.Major := i; F.Minor := i+1; F.Release := i+2; F.Build := i+3; F.Main := IntToStr(i+1000); F.Detailed := IntToStr(2000-i); F.BuildDateTime := 36215.12; F.BuildYear := i+2011; end; begin W := TTextWriter.CreateOwnedStream; // validate TIntegerDynArray AIP.Init(TypeInfo(TIntegerDynArray),AI); ................................................................................ Check(RecordEquals(F,AF[100],TypeInfo(TFileVersion))); for i := 0 to 1000 do with AF[i] do begin Check(Major=i); Check(Minor=i+1); Check(Release=i+2); Check(Build=i+3); Check(Main=IntToStr(i+1000)); Check(Detailed=IntToStr(2000-i)); CheckSame(BuildDateTime,36215.12); Check(BuildYear=i+2011); end; for i := 0 to 1000 do begin Fill(F,i); Check(AFP.IndexOf(F)=i); end; ................................................................................ {$endif} for i := 1 to 10000 do begin j := Random(maxInt)-Random(maxInt); str(j,a); s := RawUTF8(a); Check(kr32(0,pointer(s),length(s))=kr32pas(pointer(s),length(s))); u := string(a); Check(IntToStr(j)=u); Check(Int32ToUtf8(j)=s); Check(format('%d',[j])=u); Check(GetInteger(pointer(s))=j); Check(FormatUTF8('%',[j])=s); k := Int64(j)*Random(MaxInt); str(k,a); s := RawUTF8(a); u := string(a); Check(IntToStr(k)=u); Check(Int64ToUtf8(k)=s); Check(format('%d',[k])=u); Check(FormatUTF8('%',[k])=s); err := 1; l := GetInt64(pointer(s),err); Check((err=0)and(l=k)); str(j,a); Check(IntToStr(j)=string(a)); Check(format('%d',[j])=string(a)); Check(format('%.8x',[j])=IntToHex(j,8)); d := Random*1E-17-Random*1E-9; str(d,a); s := RawUTF8(a); e := GetExtended(Pointer(s),err); Check(SameValue(e,d)); // test str() ................................................................................ Check(T.AddField('double',tftDouble)<>nil); Check(T.AddField('varint',tftVarUInt32)<>nil); Check(T.AddField('text',tftUTF8,[tfoUnique])<>nil); Check(T.AddField('ansi',tftWinAnsi,[])<>nil); Check(T.AddField('currency',tftCurrency)<>nil); Test; FN := ChangeFileExt(paramstr(0),'.syntable'); f := FileCreate(FN); // manual storage of TSynTable header W.AssignToFile(f); T.SaveTo(W); W.Flush; FileClose(f); T.Free; f := FileOpen(FN,fmOpenRead); ................................................................................ WriteVarUInt32(ValuesCount); if ValuesCount=0 then exit; PI := pointer(Values); PByteArray(fBuf)^[fPos] := ord(DataLayout); inc(fPos); inc(fTotalWritten); if DataLayout=wkOffset then begin pos := fPos; fPos := PtrUInt(ToVarUInt32(PI^[0],@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf); diff := PI^[1]-PI^[0]; inc(PtrUInt(PI),4); dec(ValuesCount); if ValuesCount=0 then begin inc(fTotalWritten,PtrUInt(fPos-pos)); ................................................................................ wkUInt32: begin n := (fBufLen-fPos)shr 2; if ValuesCount<n then n := ValuesCount; Move(PI^,P^,n*4); inc(P,n*4); end; wkVarInt32, wkVarUInt32,wkOffset: begin PBeg := PAnsiChar(P); // leave space for chunk size inc(P,4); n := ValuesCount; case DataLayout of wkVarInt32: for i := 0 to ValuesCount-1 do begin P := ToVarInt32(PI^[i],P); ................................................................................ for i := 0 to ValuesCount-1 do begin P := ToVarUInt32(PI^[i],P); if PtrUInt(P)>=PtrUInt(PEnd) then begin n := i+1; break; // avoid buffer overflow end; end; wkOffset: for i := 0 to ValuesCount-1 do begin P := ToVarUInt32(PI^[i]-PI^[i-1],P); if PtrUInt(P)>=PtrUInt(PEnd) then begin n := i+1; break; // avoid buffer overflow end; end; end; PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32s end; wkSorted: begin PBeg := PAnsiChar(P)+4; // leave space for chunk size P := PByte(CleverStoreInteger(pointer(PI),PBeg,PEnd,ValuesCount,n)); ................................................................................ end; end; procedure TFileBufferReader.ErrorInvalidContent; begin raise Exception.Create('TFileBufferReader: invalid content'); end; procedure TFileBufferReader.Open(aFile: THandle); begin fCurrentPos := 0; fBuf := nil; fMap := 0; fFile := aFile; ................................................................................ end; end; function TFileBufferReader.Read(Data: pointer; DataLen: integer): integer; var len: integer; begin if DataLen>0 then if fMap<>0 then begin // file up to 2 GB: use fast memory map len := fBufSize-fCurrentPos; if len>DataLen then len := DataLen; move(fBuf[fCurrentPos],Data^,len); inc(fCurrentPos,len); result := len; ................................................................................ ErrorInvalidContent; PEnd := pointer(PtrUInt(P)+PtrUInt(len)); end; function TFileBufferReader.ReadPointer(DataLen: PtrUInt; var aTempData: RawByteString): pointer; begin if fMap=0 then begin // read from file if DataLen>PtrUInt(Length(aTempData)) then begin aTempData := ''; // so no move() call in SetLength() below SetLength(aTempData,DataLen); end; if PtrUInt(FileRead(fFile,pointer(aTempData)^,DataLen))<>DataLen then result := nil else // invalid content ................................................................................ {$ifndef LVCL} function TFileBufferReader.ReadStream: TCustomMemoryStream; var DataLen: PtrUInt; begin DataLen := ReadVarUInt32; if DataLen<>0 then if fMap=0 then begin // use temporary TMemoryStream only if not in memory map buffer result := TMemoryStream.Create; result.Size := DataLen; if PtrUInt(FileRead(fFile,result.Memory^,DataLen))<>DataLen then FreeAndNil(result); // invalid content end else if DataLen+fCurrentPos>fBufSize then ................................................................................ // DataLen=0 -> invalid content result := nil; end; {$endif} function TFileBufferReader.ReadByte: PtrUInt; begin if fMap<>0 then if fCurrentPos>=fBufSize then // invalid request result := 0 else begin // read one byte from current memory map result := ord(fBuf[fCurrentPos]); inc(fCurrentPos); end else begin // read from file if >= 2 GB (slow, but works) result := 0; if FileRead(fFile,result,1)<>1 then result := 0; end; end; function TFileBufferReader.ReadVarUInt32: PtrUInt; var c, n: PtrUInt; begin result := ReadByte; if result>$7f then begin n := 0; ................................................................................ if result=0 then exit; count := result; if count>length(Values) then // only set length is not big enough SetLength(Values,count); PI := pointer(Values); DataLayout := TFileBufferWriterKind(ReadByte); if DataLayout=wkOffset then begin PI^ := ReadVarUInt32; dec(count); diff := ReadVarUInt32; if diff<>0 then begin for i := 0 to count-1 do PIA^[i+1] := PIA^[i]+diff; exit; ................................................................................ inc(PI); end; wkSorted: begin n := CleverReadInteger(pointer(P),pointer(PEnd),PI); dec(count,n); inc(PtrUInt(PI),n*4); end; wkOffset: begin while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin PIA^[1] := PIA^[0]+integer(FromVarUInt32(P)); dec(count); inc(PI); end; if count<=0 then inc(PI); // make sure PI=@Values[result] end; else ................................................................................ assert((count=0)and(PI=@Values[result])); end; function TFileBufferReader.ReadRawUTF8List(List: TRawUTF8List): boolean; var i: integer; StoreObjectsAsVarUInt32: Boolean; begin if (fMap<>0) and (List<>nil) then with List do begin BeginUpdate; try Capacity := 0; // finalize both fObjects[] and fList[] fCount := ReadVarRawUTF8DynArray(List.fList); result := true; if fCount=0 then ................................................................................ end; {$ifndef CPU64} function TFileBufferReader.Seek(Offset: Int64): boolean; begin if (Offset<0) or (Offset>fFileSize) then result := False else if fMap=0 then result := FileSeek64(fFile,Offset,soFromBeginning)=Offset else begin fCurrentPos := Int64Rec(Offset).Lo; result := true; end; end; {$endif CPU64} function TFileBufferReader.Seek(Offset: PtrInt): boolean; begin // we don't need to handle fMap=0 here if fMap=0 then Result := FileSeek(fFile,Offset,0)=Offset else if (fMap<>0) and (PtrUInt(Offset)<PPtrUInt(@fFileSize)^) then begin fCurrentPos := Offset; result := true; end else result := false; end; ................................................................................ inc(P,Len); inc(TotalLen,Len); Lens[F] := Len; end else inc(TotalLen,fDefaultFieldLength); // create new record content P := RecordBuffer; Setlength(Result,TotalLen); Dest := pointer(Result); for F := 0 to fField.Count-1 do with TSynTableFieldProperties(fField.List[F]) do if F in AvailableFields then begin Len := Lens[F]; move(P^,Dest^,Len); inc(P,Len); ................................................................................ Head: integer; begin if FieldType<>tftBlobInternal then result := '' else if (Value=nil) or (ValueLen=0) then result := #0 else begin // inlined ToSBFStr() code Head := PAnsiChar(ToVarUInt32(ValueLen,@tmp))-tmp; SetLength(result,ValueLen+Head); Move(tmp,PByteArray(Result)[0],Head); Move(Value^,PByteArray(Result)[Head],ValueLen); end; end; function TSynTableFieldProperties.SBFFloat(const Value: Double): TSBFString; begin ................................................................................ function TSynTableData.ValidateSBFValue(RecordIndex: integer): string; begin CheckVTableInitialized; Result := VTable.Validate(Pointer(VValue),RecordIndex); end; { TSynLogFamily } type TExceptProc = procedure(Obj: TObject; Addr: Pointer); var /// internal list of registered TSynLogFamily // - up to MAX_SYNLOGFAMILY+1 families may be defined SynLogFamily: TObjectList = nil; threadvar CurrentHandleExceptionSynLog: TSynLog; var SynLogExceptionEnabled: boolean = false; type PExceptionRecord = ^TExceptionRecord; TExceptionRecord = packed record ExceptionCode : LongWord; ExceptionFlags : LongWord; OuterException : PExceptionRecord; ExceptionAddress : Pointer; ................................................................................ PExceptionInfo = ^TExceptionInfo; TExceptionInfo = packed record ExceptionRecord: PExceptionRecord; ContextRecord: pointer; end; GetExceptionClass = function(P: PExceptionRecord): ExceptClass; function GetHandleExceptionSynLog: TSynLog; var Index: ^TSynLogFileIndex; i: integer; ndx, n: cardinal; begin result := CurrentHandleExceptionSynLog; if result<>nil then exit; Index := @SynLogFileIndex; if SynLogFile<>nil then begin n := SynLogFile.Count; for i := 0 to high(Index^) do begin ndx := Index^[i]-1; if ndx<=n then begin result := TSynLog(SynLogFile.List^[ndx]); if result.fFamily.fHandleExceptions then exit; end; end; for i := 0 to n-1 do begin result := TSynLog(SynLogFile.List^[i]); if result.fFamily.fHandleExceptions then exit; end; end; result := nil; end; function SynLogVectoredHandler(ExceptionInfo : PExceptionInfo): PtrInt; stdcall; const EXCEPTION_CONTINUE_SEARCH = 0; var SynLog: TSynLog; Level: TSynLogInfo; E: ExceptClass; begin // guess if was a Delphi or OS exception E := nil; Level := sllException; if SynLogExceptionEnabled and (ExceptionInfo<>nil) and (ExceptionInfo^.ExceptionRecord<>nil) then with ExceptionInfo^.ExceptionRecord^ do if (ExceptObject<>nil) and ExceptObject.InheritsFrom(Exception) then // Delphi exception E := PPointer(ExceptObject)^ else begin // OS exception -> translate into a Delphi Exception class if Assigned(ExceptClsProc) then E := GetExceptionClass(ExceptClsProc)(ExceptionInfo^.ExceptionRecord); if E=nil then E := EExternalException; Level := sllExceptionOS; end; // log corresponding message if necessary if E<>nil then begin SynLog := GetHandleExceptionSynLog; if SynLog<>nil then case Level of sllException: if sllException in SynLog.fFamily.Level then with ExceptionInfo^.ExceptionRecord^ do SynLog.LogInternal(sllException,'% at % ("%")', [E,ExceptAddr,ExceptObject.Message],nil); sllExceptionOS: if SynLog.fFamily.fHandleExceptions then with ExceptionInfo^.ExceptionRecord^ do SynLog.LogInternal(sllExceptionOS,'% (%) at %', [E,pointer(ExceptionCode),ExceptionAddress],nil); end; end; result := EXCEPTION_CONTINUE_SEARCH; end; procedure TSynLogFamily.SetLevel(const aLevel: TSynLogInfos); var AddVectoredExceptionHandler: function(FirstHandler: cardinal; VectoredHandler: pointer): PtrInt; stdcall; begin fLevel := aLevel; fHandleExceptions := (sllExceptionOS in aLevel) or (sllException in aLevel); if not SynLogExceptionEnabled and fHandleExceptions then begin SynLogExceptionEnabled := true; AddVectoredExceptionHandler := GetProcAddress(GetModuleHandle(kernel32),'AddVectoredExceptionHandler'); if Assigned(AddVectoredExceptionHandler) then // available since Windows XP AddVectoredExceptionHandler(0,@SynLogVectoredHandler); end; end; constructor TSynLogFamily.Create(aSynLog: TSynLogClass); begin fSynLogClass := aSynLog; if SynLogFamily=nil then begin SynLogFamily := TObjectList.Create; GarbageCollector.Add(SynLogFamily); end; fIdent := SynLogFamily.Add(self); fDestinationPath := ExtractFilePath(paramstr(0)); fBufferSize := 4096; end; function TSynLogFamily.CreateSynLog: TSynLog; begin result := fSynLogClass.Create(self); if fPerThreadLog then begin if SynLogFile=nil then begin ................................................................................ AutoFlushThread: THandle = 0; AutoFlushSecondElapsed: cardinal; procedure AutoFlushProc(P: pointer); stdcall; // TThread not needed here var i: integer; begin repeat Sleep(1000); if SynLogFile=nil then continue; inc(AutoFlushSecondElapsed); for i := 0 to SynLogFile.Count-1 do with TSynLog(SynLogFile.List^[i]) do if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then if fWriter.B-fWriter.fTempBuf>1 then begin if not IsMultiThread and not fWriterStream.InheritsFrom(TFileStream) then IsMultiThread := true; // only TFileStream is thread-safe Flush(false); end; until false; end; procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal); var ID: cardinal; begin SynLog.CreateLocker; fAutoFlush := TimeOut; if AutoFlushThread=0 then begin AutoFlushSecondElapsed := 0; AutoFlushThread := CreateThread(nil,0,@AutoFlushProc,nil,0,ID); end; end; destructor TSynLogFamily.Destroy; begin FreeAndNil(fGlobalLog); if AutoFlushThread<>0 then begin CloseHandle(AutoFlushThread); // release background thread once for all AutoFlushThread := 0; end; inherited; end; function TSynLogFamily.SynLog: TSynLog; var ndx: integer; begin if fPerThreadLog then begin ................................................................................ ndx := SynLogFileIndex[fIdent]-1; if ndx>=0 then result := SynLogFile.List^[ndx] else result := CreateSynLog; end else if fGlobalLog<>nil then result := fGlobalLog else result := CreateSynLog; if fHandleExceptions then CurrentHandleExceptionSynLog := result; end; { TSynLog } ................................................................................ inc(fRecursionCount); result := fRecursionCount; end; function TSynLog._Release: Integer; begin dec(fRecursionCount); if sllLeave in fFamily.Level then DoEnterLeave(sllLeave); result := fRecursionCount; end; constructor TSynLog.Create(aFamily: TSynLogFamily); begin fFamily := aFamily; end; ................................................................................ begin inc(fRecursionMax,256+fRecursionMax shr 3); ReallocMem(fRecursion,fRecursionMax*sizeof(fRecursion^[0])); end; class function TSynLog.Enter(aClassType: TClass; aMethodName: PUTF8Char): ISynLog; var aSynLog: TSynLog; begin aSynLog := Family.SynLog; with aSynLog do begin // recursively store parameters if fRecursionCount=fRecursionMax then RecursionGrow; with fRecursion^[fRecursionCount] do begin Instance := nil; ClassType := aClassType; Method := aMethodName; end; end; // copy to ISynLog interface -> will call TSynLog._AddRef result := aSynLog; end; class function TSynLog.Enter(aInstance: TObject; aMethodName: PUTF8Char): ISynLog; var aSynLog: TSynLog; aFamily: TSynLogFamily; begin // inlined aSynLog := Family.SynLog aFamily := PPointer(PtrInt(Self)+vmtAutoTable)^; if aFamily=nil then aSynLog := FamilyCreate.SynLog else aSynLog := aFamily.SynLog; // recursively store parameters with aSynLog do begin ................................................................................ RecursionGrow; with fRecursion^[fRecursionCount] do begin Instance := aInstance; if aInstance=nil then ClassType := pointer(aInstance) else ClassType := PPointer(aInstance)^; Method := aMethodName; end; end; // copy to ISynLog interface -> will call TSynLog._AddRef result := aSynLog; end; class function TSynLog.FamilyCreate: TSynLogFamily; ................................................................................ procedure TSynLog.Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); begin if Level in fFamily.fLevel then LogInternal(Level,aName,aTypeInfo,aValue); end; procedure TSynLog.LogFileHeader; var Freq: Int64; begin QueryPerformanceFrequency(Freq); ExeVersionRetrieve; with ExeVersion, SystemInfo, OSVersionInfo do ................................................................................ procedure TSynLog.CreateLogWriter; begin if fWriterStream=nil then begin ExeVersionRetrieve; fFileName := string(ExeVersion.ProgramName)+' '+GetCaptionFromClass(ClassType)+' '+ TFileName(NowToString(false)); if fFamily.PerThreadLog then fFileName := fFileName+' '+IntToStr(GetCurrentThreadId); fFileName := fFamily.fDestinationPath+fFileName+'.log'; fWriterStream := TFileStream.Create(fFileName,fmCreate) end; if fWriter=nil then fWriter := TTextWriter.Create(fWriterStream,fFamily.BufferSize); end; ................................................................................ if Instance<>nil then begin fWriter.Add('('); fWriter.AddPointer(PtrUInt(Instance)); fWriter.Add(')'); end; fWriter.Add('.'); end; fWriter.AddNoJSONEscape(Method); end; fWriter.AddCR; end; procedure TSynLog.DoEnterLeave(aLevel: TSynLogInfo); begin LogHeaderLock(aLevel); |
| > > > > | > > > > > > > > > > > > > > > > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > > > > > > | > > > > > > > > > > > > > > > | > > > > > > > > > > > | | | > > > > | | | > > > > > > > > > > > > > > > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | < | < | | | | | | > > > > > > > > > | > > > > | > > > > | | > > > > < | | | | | | | | | | | > | | | > > > > > > > > > > > > > > > > > | | | | > > > > > > > > > > > > > > > > > | | | > > > > > > > > > | | > | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | < < < < < < < < < < < < < < < < < < < < < < < < > > > > > > > > > > < > | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > > | | > | > > > > | | < < < > > > > > > > > > > > > > > > > > > > > > | > > > > > > > > | < < < > > > > > > > > > > | > > > > > > > > | < > | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > < < > > > > > | < > > > > > > > | | | > | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | > | > |
134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 ... 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 ... 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 .... 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 .... 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 .... 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 .... 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 .... 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 .... 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 .... 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 .... 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 .... 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 .... 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 .... 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 .... 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 .... 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 .... 5933 5934 5935 5936 5937 5938 5939 5940 5941 5942 5943 5944 5945 5946 5947 5948 5949 5950 5951 5952 5953 5954 5955 5956 5957 5958 5959 5960 5961 5962 5963 5964 5965 5966 5967 5968 5969 5970 5971 5972 5973 5974 5975 5976 5977 5978 5979 5980 5981 5982 5983 5984 5985 5986 5987 5988 5989 5990 5991 5992 5993 5994 5995 5996 .... 6047 6048 6049 6050 6051 6052 6053 6054 6055 6056 6057 6058 6059 6060 6061 .... 6694 6695 6696 6697 6698 6699 6700 6701 6702 6703 6704 6705 6706 6707 6708 6709 6710 6711 6712 6713 6714 6715 6716 6717 6718 6719 6720 6721 6722 6723 6724 6725 6726 6727 6728 6729 6730 6731 6732 6733 6734 6735 6736 6737 6738 6739 6740 6741 6742 6743 6744 6745 6746 6747 6748 6749 6750 6751 6752 6753 6754 6755 6756 6757 6758 6759 6760 6761 6762 6763 .... 8492 8493 8494 8495 8496 8497 8498 8499 8500 8501 8502 8503 8504 8505 8506 8507 8508 8509 8510 8511 8512 8513 8514 8515 8516 8517 8518 8519 8520 8521 8522 8523 8524 8525 8526 .... 9475 9476 9477 9478 9479 9480 9481 9482 9483 9484 9485 9486 9487 9488 9489 .... 9496 9497 9498 9499 9500 9501 9502 9503 9504 9505 9506 9507 9508 9509 9510 ..... 12062 12063 12064 12065 12066 12067 12068 12069 12070 12071 12072 12073 12074 12075 12076 12077 12078 12079 12080 12081 12082 12083 12084 12085 12086 12087 12088 12089 12090 12091 12092 12093 12094 12095 12096 12097 12098 ..... 12379 12380 12381 12382 12383 12384 12385 12386 12387 12388 12389 12390 12391 12392 12393 12394 12395 12396 12397 12398 12399 12400 12401 12402 12403 12404 12405 12406 ..... 13196 13197 13198 13199 13200 13201 13202 13203 13204 13205 13206 13207 13208 13209 13210 13211 ..... 13934 13935 13936 13937 13938 13939 13940 13941 13942 13943 13944 13945 13946 13947 13948 13949 13950 13951 13952 13953 13954 13955 ..... 15018 15019 15020 15021 15022 15023 15024 15025 15026 15027 15028 15029 15030 15031 15032 ..... 15036 15037 15038 15039 15040 15041 15042 15043 15044 15045 15046 15047 15048 15049 15050 15051 15052 15053 15054 15055 15056 15057 15058 15059 15060 15061 15062 15063 15064 15065 15066 15067 15068 15069 15070 15071 15072 15073 ..... 15080 15081 15082 15083 15084 15085 15086 15087 15088 15089 15090 15091 15092 15093 15094 15095 15096 15097 ..... 15127 15128 15129 15130 15131 15132 15133 15134 15135 15136 15137 15138 15139 15140 15141 ..... 15144 15145 15146 15147 15148 15149 15150 15151 15152 15153 15154 15155 15156 15157 15158 ..... 15162 15163 15164 15165 15166 15167 15168 15169 15170 15171 15172 15173 15174 15175 15176 15177 15178 15179 ..... 17189 17190 17191 17192 17193 17194 17195 17196 17197 17198 17199 17200 17201 17202 17203 ..... 17714 17715 17716 17717 17718 17719 17720 17721 17722 17723 17724 17725 17726 17727 17728 17729 17730 17731 17732 17733 17734 17735 17736 17737 17738 17739 17740 17741 17742 17743 ..... 17771 17772 17773 17774 17775 17776 17777 17778 17779 17780 17781 17782 17783 17784 17785 17786 ..... 17999 18000 18001 18002 18003 18004 18005 18006 18007 18008 18009 18010 18011 18012 18013 18014 ..... 18306 18307 18308 18309 18310 18311 18312 18313 18314 18315 18316 18317 18318 18319 18320 18321 18322 18323 18324 18325 18326 18327 18328 18329 18330 18331 18332 18333 18334 18335 18336 18337 ..... 18526 18527 18528 18529 18530 18531 18532 18533 18534 18535 18536 18537 18538 18539 18540 ..... 19548 19549 19550 19551 19552 19553 19554 19555 19556 19557 19558 19559 19560 19561 19562 ..... 19584 19585 19586 19587 19588 19589 19590 19591 19592 19593 19594 19595 19596 19597 19598 ..... 19605 19606 19607 19608 19609 19610 19611 19612 19613 19614 19615 19616 19617 19618 19619 19620 19621 19622 19623 19624 19625 19626 19627 19628 19629 19630 19631 19632 19633 ..... 19738 19739 19740 19741 19742 19743 19744 19745 19746 19747 19748 19749 19750 19751 19752 19753 19754 19755 19756 19757 19758 19759 19760 ..... 19775 19776 19777 19778 19779 19780 19781 19782 19783 19784 19785 19786 19787 19788 19789 ..... 19814 19815 19816 19817 19818 19819 19820 19821 19822 19823 19824 19825 19826 19827 19828 ..... 19839 19840 19841 19842 19843 19844 19845 19846 19847 19848 19849 19850 19851 19852 19853 ..... 19860 19861 19862 19863 19864 19865 19866 19867 19868 19869 19870 19871 19872 19873 19874 19875 19876 19877 19878 19879 19880 19881 19882 19883 19884 19885 19886 19887 19888 19889 19890 19891 19892 19893 19894 19895 19896 19897 19898 19899 19900 19901 19902 19903 19904 ..... 19991 19992 19993 19994 19995 19996 19997 19998 19999 20000 20001 20002 20003 20004 20005 ..... 20023 20024 20025 20026 20027 20028 20029 20030 20031 20032 20033 20034 20035 20036 20037 20038 20039 20040 20041 20042 20043 20044 20045 20046 20047 20048 ..... 20102 20103 20104 20105 20106 20107 20108 20109 20110 20111 20112 20113 20114 20115 20116 ..... 20168 20169 20170 20171 20172 20173 20174 20175 20176 20177 20178 20179 20180 20181 20182 20183 20184 20185 20186 20187 20188 20189 20190 20191 20192 20193 20194 20195 ..... 20908 20909 20910 20911 20912 20913 20914 20915 20916 20917 20918 20919 20920 20921 20922 ..... 21591 21592 21593 21594 21595 21596 21597 21598 21599 21600 21601 21602 21603 21604 21605 ..... 22413 22414 22415 22416 22417 22418 22419 22420 22421 22422 22423 22424 22425 22426 22427 22428 22429 22430 22431 22432 22433 22434 22435 22436 22437 22438 22439 22440 22441 22442 22443 22444 22445 22446 22447 22448 22449 22450 22451 22452 22453 22454 22455 22456 22457 22458 22459 22460 22461 22462 22463 22464 22465 22466 22467 22468 22469 22470 22471 22472 22473 22474 22475 22476 22477 22478 22479 22480 22481 22482 22483 22484 22485 22486 22487 22488 22489 22490 22491 22492 22493 22494 22495 22496 22497 22498 22499 22500 22501 22502 22503 22504 22505 22506 22507 22508 22509 22510 22511 22512 22513 22514 22515 22516 22517 22518 22519 22520 22521 22522 22523 22524 22525 22526 22527 22528 22529 22530 22531 22532 22533 22534 22535 22536 22537 22538 22539 22540 22541 22542 22543 22544 22545 22546 22547 22548 22549 22550 22551 22552 22553 22554 22555 22556 22557 22558 22559 22560 22561 22562 22563 22564 22565 22566 22567 22568 22569 22570 22571 22572 22573 22574 22575 22576 22577 22578 22579 22580 22581 22582 22583 22584 22585 22586 22587 22588 22589 22590 22591 22592 22593 22594 22595 22596 22597 22598 22599 22600 22601 22602 22603 22604 22605 22606 22607 22608 22609 22610 22611 22612 22613 22614 22615 22616 22617 22618 22619 22620 22621 22622 22623 22624 22625 22626 22627 22628 22629 22630 22631 22632 22633 22634 22635 22636 22637 22638 22639 22640 22641 22642 22643 22644 22645 22646 22647 22648 22649 22650 22651 22652 22653 22654 22655 22656 22657 22658 22659 22660 22661 22662 22663 22664 22665 22666 22667 22668 22669 22670 22671 22672 22673 22674 22675 22676 22677 22678 22679 22680 22681 22682 22683 22684 22685 22686 22687 22688 22689 22690 22691 22692 22693 22694 22695 22696 22697 22698 22699 22700 22701 22702 22703 22704 22705 22706 22707 22708 22709 22710 22711 22712 22713 22714 22715 22716 22717 22718 22719 22720 22721 22722 22723 22724 22725 22726 22727 22728 22729 22730 22731 22732 22733 22734 22735 22736 22737 22738 22739 22740 22741 22742 22743 22744 22745 22746 22747 22748 22749 22750 22751 22752 22753 22754 22755 22756 22757 22758 22759 22760 22761 22762 22763 22764 22765 22766 22767 22768 22769 22770 22771 22772 22773 22774 22775 22776 22777 22778 22779 22780 22781 22782 22783 22784 22785 22786 22787 22788 22789 22790 22791 22792 22793 22794 22795 22796 22797 22798 22799 22800 22801 22802 22803 22804 22805 22806 22807 22808 22809 22810 22811 22812 22813 22814 22815 22816 22817 22818 22819 22820 22821 22822 22823 22824 22825 22826 22827 22828 22829 22830 22831 22832 22833 22834 22835 22836 22837 22838 22839 22840 22841 22842 22843 22844 22845 22846 22847 22848 22849 22850 22851 22852 22853 22854 22855 22856 22857 22858 22859 22860 22861 22862 22863 22864 22865 22866 22867 22868 22869 22870 22871 22872 22873 22874 22875 22876 22877 22878 22879 22880 22881 22882 22883 22884 22885 22886 22887 22888 22889 22890 22891 22892 22893 22894 22895 22896 22897 22898 22899 22900 22901 22902 22903 22904 22905 22906 22907 22908 22909 22910 22911 22912 22913 22914 22915 22916 22917 22918 22919 22920 22921 22922 22923 22924 22925 22926 22927 22928 22929 22930 22931 22932 22933 22934 22935 22936 22937 22938 22939 22940 22941 22942 22943 22944 22945 22946 22947 22948 22949 22950 22951 22952 22953 22954 22955 22956 22957 22958 22959 22960 22961 22962 ..... 22968 22969 22970 22971 22972 22973 22974 22975 22976 22977 22978 22979 22980 22981 22982 22983 22984 22985 22986 22987 22988 22989 22990 22991 22992 22993 22994 22995 22996 22997 22998 22999 23000 23001 23002 23003 23004 23005 23006 23007 23008 23009 23010 23011 23012 23013 23014 23015 23016 23017 23018 23019 23020 23021 23022 23023 23024 23025 23026 23027 23028 23029 23030 23031 23032 23033 23034 23035 23036 23037 23038 23039 23040 23041 23042 23043 23044 23045 23046 23047 23048 23049 23050 23051 23052 23053 23054 23055 23056 23057 23058 23059 23060 23061 23062 23063 23064 23065 23066 23067 23068 23069 23070 23071 23072 23073 23074 23075 23076 23077 23078 23079 23080 23081 23082 23083 23084 23085 23086 23087 23088 23089 23090 23091 23092 23093 23094 23095 23096 23097 23098 23099 23100 23101 23102 23103 23104 23105 23106 23107 23108 23109 23110 23111 23112 23113 23114 23115 23116 23117 23118 23119 23120 23121 23122 23123 23124 23125 23126 23127 23128 23129 23130 23131 23132 23133 23134 23135 23136 23137 23138 23139 23140 23141 23142 23143 23144 23145 23146 23147 23148 23149 23150 23151 23152 23153 23154 23155 23156 23157 23158 23159 23160 23161 23162 23163 23164 23165 23166 23167 23168 23169 23170 23171 23172 23173 23174 23175 23176 23177 23178 23179 23180 23181 23182 23183 23184 23185 23186 23187 23188 23189 23190 23191 23192 23193 23194 23195 23196 23197 23198 23199 23200 23201 23202 23203 23204 23205 23206 23207 23208 23209 23210 23211 23212 23213 23214 23215 23216 23217 23218 23219 23220 23221 23222 23223 23224 23225 23226 23227 23228 23229 ..... 23239 23240 23241 23242 23243 23244 23245 23246 23247 23248 23249 23250 23251 23252 23253 23254 23255 23256 23257 23258 23259 23260 23261 23262 23263 23264 23265 23266 23267 23268 23269 23270 23271 23272 23273 23274 23275 23276 23277 23278 23279 23280 23281 23282 23283 23284 23285 23286 23287 23288 ..... 23289 23290 23291 23292 23293 23294 23295 23296 23297 23298 23299 23300 23301 23302 23303 ..... 23308 23309 23310 23311 23312 23313 23314 23315 23316 23317 23318 23319 23320 23321 23322 23323 23324 23325 23326 23327 23328 23329 23330 ..... 23367 23368 23369 23370 23371 23372 23373 23374 23375 23376 23377 23378 23379 23380 23381 23382 23383 23384 23385 23386 23387 23388 23389 23390 23391 23392 23393 23394 23395 23396 23397 23398 23399 23400 23401 23402 23403 23404 23405 23406 23407 23408 23409 23410 23411 23412 23413 ..... 23415 23416 23417 23418 23419 23420 23421 23422 23423 23424 23425 23426 23427 23428 23429 ..... 23491 23492 23493 23494 23495 23496 23497 23498 23499 23500 23501 23502 23503 23504 23505 23506 23507 23508 23509 23510 23511 23512 23513 23514 23515 23516 23517 23518 23519 23520 ..... 23659 23660 23661 23662 23663 23664 23665 23666 23667 23668 23669 23670 23671 23672 23673 ..... 23700 23701 23702 23703 23704 23705 23706 23707 23708 23709 23710 23711 23712 23713 23714 23715 23716 |
TSynValidateTableUniqueField instance is created if tfoUnique is in Options) - dedicated TSynTableFieldProperties.Filter method for filtering (using common TSynFilter classes, working at UTF-8 Text content) - faster implementation of Move() for Delphi versions with no FastCode inside - new ConvertCaseUTF8(), UpperCaseU(), LowerCaseU(), Int64ToUInt32(), GetCardinalDef(), IsValidEmail, IsValidIP4Address(), PatchCodePtrUInt(), GetCaptionFromClass(), GetDisplayNameFromClass(), DateTimeToIso8601Text() StrUInt32(), StringBufferToUtf8(), IsZero(), AddPrefixToCSV(), IntToString() procedures or functions (with associated tests) - new grep-like IsMatch() function for basic pattern matching - new BinToBase64, Base64ToBin and IsBase64 *fast* conversion functions (with optimized assembler version, using CPU pipelining and lookup table) - introducing the GarbageCollector TObjectList for handling a global garbage collector for instances which must live during the whole executable process (used e.g. to avoid a memory leak for "class var" or such variables) - new TSynLog class to handle enhanced logging to any application, with exception handling (+stack trace) and customer-side performance profiling - new TSynMapFile class to retrieve debugging information from .map file (and able to save and read smaller .mab files) - used by TSynLog if available - great performance improvement in TSynTableFieldProperties for update process - now TTextWriter can have a custom internal buffer size (default 1024 bytes) - fixed issue in TSynTable.Data() method: ID was not set as expected - fixed issue in TSynTableFieldProperties: wrong constraint evaluation and index refresh at records update } ................................................................................ function UTF8ToWideString(const Text: RawUTF8): WideString; /// convert any UTF-8 encoded String into a generic SynUnicode Text function UTF8ToSynUnicode(const Text: RawUTF8): SynUnicode; /// convert any Ansi 7 bit encoded String into a generic VCL Text // - the Text content must contain only 7 bit pure ASCII characters function Ansi7ToString(const Text: RawByteString): string; overload; /// convert any Ansi 7 bit encoded String into a generic VCL Text // - the Text content must contain only 7 bit pure ASCII characters function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string; overload; /// convert any generic VCL Text into Ansi 7 bit encoded String // - the Text content must contain only 7 bit pure ASCII characters function StringToAnsi7(const Text: string): RawByteString; /// fast Format() function replacement, optimized for RawUTF8 // - only supported token is %, which works only for integer and string type of ................................................................................ {$ifndef PURE_PASCAL} {$ifndef ISDELPHI2007ANDUP} /// faster implementation of Move() for Delphi versions with no FastCode inside procedure Move(const Source; var Dest; Count: Integer); {$endif} {$endif} /// faster version than default SysUtils.IntToStr implementation function IntToString(Value: integer): string; overload; /// faster version than default SysUtils.IntToStr implementation function IntToString(Value: cardinal): string; overload; /// faster version than default SysUtils.IntToStr implementation function IntToString(Value: Int64): string; overload; {$ifndef FPC} { these asm function use some low-level system.pas calls } /// use our fast asm RawUTF8 version of Trim() function Trim(const S: RawUTF8): RawUTF8; /// use our fast asm version of CompareMem() ................................................................................ // - returns the index of the added element in the dynamic array // - note that because of dynamic array internal memory managment, adding // will be a bit slower than e.g. with a TList: the list is reallocated // every time a record is added - but in practice, with FastMM4 or // SynScaleMM, there is no big speed penalty - for even better speed, you // can also specify an external count variable in Init(...,@Count) method function Add(const Elem): integer; /// add an element to the dynamic array // - this version add a void element to the array, and returns its index function New: integer; /// add elements from a given dynamic array // - the supplied source DynArray MUST be of the same exact type as the // current used for this TDynArray // - you can specify the start index and the number of items to take from // the source dynamic array (leave as -1 to add till the end) procedure AddArray(const DynArray; aStartIndex: integer=0; aCount: integer=-1); /// add an element to the dynamic array at the position specified by Index ................................................................................ // - wkVarUInt32 will write the content using our 32-bit variable-length integer // encoding // - wkVarInt32 will write the content using our 32-bit variable-length integer // encoding and the by-two complement (0=0,1=1,2=-1,3=2,4=-2...) // - wkSorted will write an increasing array of integers, handling the special // case of a difference of 1 between two values (therefore is very optimized // to store an array of IDs) // - wkOffsetU and wkOffsetI will write the difference between two successive // values, handling constant difference (Unsigned or Integer) in an optimized manner TFileBufferWriterKind = (wkUInt32, wkVarUInt32, wkVarInt32, wkSorted, wkOffsetU, wkOffsetI); PFileBufferWriter = ^TFileBufferWriter; /// this structure can be used to speed up writing to a file // - big speed up if data is written in small blocks // - also handle optimized storage of any dynamic array of Integer/Int64/RawUTF8 // - is defined either as an object either as a record, due to a bug ................................................................................ // used by ReadChunk() method fBufTemp: RawByteString; /// get Isize + buffer from current memory map or fBufTemp into (P,PEnd) procedure ReadChunk(var P, PEnd: PByte); public /// initialize the buffer, and specify a file to use for reading procedure Open(aFile: THandle); /// initialize the buffer from an already existing memory block // - may be e.g. a resource procedure OpenFrom(aBuffer: pointer; aBufferSize: cardinal); /// close all internal mapped files // - call Open() again to use the Read() methods procedure Close; {$ifndef CPU64} /// change the current reading position, from the beginning of the file // - returns TRUE if success, or FALSE if Offset is out of range function Seek(Offset: Int64): boolean; overload; ................................................................................ function Read(Data: pointer; DataLen: integer): integer; overload; /// read some UTF-8 encoded text at the current position // - returns the resulting text length, in bytes function Read(out Text: RawUTF8): integer; overload; /// read one byte // - if reached end of file, don't raise any error, but returns 0 function ReadByte: PtrUInt; {$ifdef HASINLINE}inline;{$endif} /// read one cardinal, which was written as fixed length // - if reached end of file, don't raise any error, but returns 0 function ReadCardinal: cardinal; /// read one cardinal value encoded using our 32-bit variable-length integer function ReadVarUInt32: PtrUInt; /// read one integer value encoded using our 32-bit variable-length integer, // and the by-two complement function ReadVarInt32: PtrInt; /// read one UInt64 value encoded using our 64-bit variable-length integer function ReadVarUInt64: QWord; ................................................................................ // in big-endian order (most-signignifican byte first): use it for display procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: integer); /// fast conversion from a pointer data into hexa chars, ready to be displayed // - use internally BinToHexDisplay() function PointerToHex(aPointer: Pointer): RawUTF8; /// fast conversion from hexa chars into a pointer function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: integer): boolean; /// fast conversion from binary data into Base64 encoded text function BinToBase64(const s: RawByteString): RawByteString; overload; /// fast conversion from binary data into Base64 encoded text function BinToBase64(Bin: PAnsiChar; BinBytes: integer): RawByteString; overload; /// fast conversion from binary data into Base64 encoded text ................................................................................ function GetDelphiCompilerVersion: RawUTF8; { ************ Logging classes and functions } type /// a debugger symbol, as decoded by TSynMapFile from a .map file TSynMapSymbol = record /// symbol internal name Name: RawUTF8; /// starting offset of this symbol in the executable Start: cardinal; /// end offset of this symbol in the executable Stop: cardinal; end; PSynMapSymbol = ^TSynMapSymbol; /// a dynamic array of symbols, as decoded by TSynMapFile from a .map file TSynMapSymbolDynArray = array of TSynMapSymbol; /// a debugger unit, as decoded by TSynMapFile from a .map file TSynMapUnit = record /// Name, Start and Stop of this Unit Symbol: TSynMapSymbol; /// list of all mapped source code lines of this unit Line: TIntegerDynArray; /// start code address of each source code lin Addr: TIntegerDynArray; end; /// a dynamic array of units, as decoded by TSynMapFile from a .map file TSynMapUnitDynArray = array of TSynMapUnit; /// retrieve a .map file content, to be used e.g. with TSynLog to provide // additional debugging information // - original .map content can be saved as .mab file in a more optimized format TSynMapFile = class protected fMapFile: TFileName; fSymbol: TSynMapSymbolDynArray; fUnit: TSynMapUnitDynArray; fSymbols: TDynArray; fUnits: TDynArrayHashed; fGetModuleHandle: PtrUInt; fHasDebugInfo: boolean; public /// get the available debugging information // - will first search for a .map file in the .exe directory: if found, // will be read to retrieve all necessary debugging information - a .mab // file will be also created in the same directory (if MabCreate is TRUE) // - if .map is not not available, will search for the .mab file in the // .exe directory // - if no .mab is available, will search for a .mab appended to the exe // - if nothing is available, will log as hexadecimal pointers, without // debugging information // - if aExeName is not specified, will use the current process executable constructor Create(const aExeName: TFileName=''; MabCreate: boolean=true); /// save all debugging information in the .mab custom binary format // - if no file name is specified, it will be saved as ExeName.mab // - this file content can be appended to the executable via SaveToExe method // - this function returns the created file name function SaveToFile(const aFileName: TFileName=''): TFileName; /// append all debugging information to an executable // - the executable name must be specified, because it's impossible to // write to the executable of a running process procedure SaveToExe(const aExeName: TFileName); /// add some debugging information according to the specified memory address // - will create a global TSynMapFile instance for the current process, if // necessary // - if no debugging information is available (.map or .mab), will write // the address as hexadecimal class procedure Log(W: TTextWriter; Addr: PtrUInt); /// retrieve a symbol according to an absolute code address function FindSymbol(aAddr: cardinal): integer; /// retrieve an unit and source line, according to an absolute code address function FindUnit(aAddr: cardinal; out LineNumber: integer): integer; /// all symbols associated to the executable property Symbols: TSynMapSymbolDynArray read fSymbol; /// all units, including line numbers, associated to the executable property Units: TSynMapUnitDynArray read fUnit; /// equals true if a .map or .mab debugging information has been loaded property HasDebugInfo: boolean read fHasDebugInfo; end; /// the available logging events, as handled by TSynLog // - sllError will log errors // - sllInfo will log general information events // - sllDebug will log detailed debugging information // - sllLastError will log the GetLastError OS message // - sllException will log all exception raised - available since Windows XP // - sllExceptionOS will log all OS low-level exceptions (EDivByZero, ................................................................................ Instance: TObject=nil); overload; /// call this method to add some information to the log at a specified level // - if Instance is set and Text is not '', it will log the corresponding // class name and address (to be used e.g. if you didn't call TSynLog.Enter() // method first) // - if Instance is set and Text is '', will behave the same as // Log(Level,Instance), i.e. write the Instance as JSON content procedure Log(Level: TSynLogInfo; const Text: RawUTF8; Instance: TObject=nil); overload; /// call this method to add the content of an object to the log at a // specified level // - TSynLog will write the class and hexa address - TSQLLog will write the // object JSON content procedure Log(Level: TSynLogInfo; Instance: TObject); overload; /// call this method to add the content of most low-level types to the log // at a specified level // - TSynLog will handle enumerations and dynamic array; TSQLLog will be // able to write TObject/TSQLRecord and sets content as JSON procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); overload; /// call this method to add the caller address to the log at the specified level // - if the debugging info is available from TSynMapFile, will log the // unit name, associated symbol and source code line procedure Log(Level: TSynLogInfo); overload; /// retrieve the associated logging instance function Instance: TSynLog; end; {{ regroup several logs under an unique family name - you should usualy use one family per application or per architectural module: e.g. a server application may want to log in separate files the ................................................................................ fIdent: integer; fDestinationPath: TFileName; fBufferSize: integer; fHRTimeStamp: boolean; fWithUnitName: boolean; fAutoFlush: cardinal; fHandleExceptions: boolean; fStackTraceLevel: cardinal; fExceptionIgnore: TList; function CreateSynLog: TSynLog; procedure SetAutoFlush(TimeOut: cardinal); procedure SetLevel(const aLevel: TSynLogInfos); public /// intialize for a TSynLog class family // - add it in the global SynLogFileFamily[] list constructor Create(aSynLog: TSynLogClass); /// release associated memory destructor Destroy; override; /// retrieve the corresponding log file of this thread and family // - creates the TSynLog if not already existing for this current thread function SynLog: TSynLog; /// you can add some exceptions to be ignored to this list // - for instance, EConvertError may be added to the list property ExceptionIgnore: TList read fExceptionIgnore; published /// the associated TSynLog class property SynLogClass: TSynLogClass read fSynLogClass; /// index in global SynLogFileFamily[] and threadvar SynLogFileIndex[] lists property Ident: integer read fIdent; /// the current level of logging information for this family property Level: TSynLogInfos read fLevel write SetLevel; ................................................................................ // disk, whatever the current content size is // - by default, the log file will be written for every 4 KB of log - this // will ensure that the main application won't be slow down by logging // - in order not to loose any log, a background thread can be created // and will be responsible of flushing all pending log content every // period of time (e.g. every 10 seconds) property AutoFlushTimeOut: cardinal read fAutoFlush write SetAutoFlush; /// the recursive depth of stack trace symbol to write // - used only if exceptions are handled // - default value is 20 property StackTraceLevel: cardinal read fStackTraceLevel write fStackTraceLevel; end; /// used to store the identification of one recursivity level TSynLogCurrentIdent = packed record Instance: TObject; ClassType: TClass; Method: PUTF8Char; Caller: PtrUInt; end; PSynLogCurrentIdent = ^TSynLogCurrentIdent; /// used to store the identification of all recursivity levels TSynLogCurrentIdents = array[0..maxInt div sizeof(TSynLogCurrentIdent)-1] of TSynLogCurrentIdent; PSynLogCurrentIdents = ^TSynLogCurrentIdents; /// a per-family and/or per-thread log file content // - you should create a sub class per kind of log file // ! TSynLogDB = class(TSynLog); // - the TSynLog instance won't be allocated in heap, but will share a // per-thread (if Family.PerThreadLog=TRUE) or global private log file instance // - was very optimized for speed, if no logging is written, and even during // log write (using an internal TTextWriter) // - can use available debugging information via the TSynMapFile class, for // stack trace logging for exceptions, and Enter/Leave labelling TSynLog = class(TObject, ISynLog) protected fWriter: TTextWriter; fFamily: TSynLogFamily; fWriterStream: TStream; fHeaderWritten: boolean; fLockCS: PRTLCriticalSection; fStartTimeStamp: Int64; fFileName: TFileName; /// set by Enter() method fRecursionCount: integer; fRecursionMax: integer; fRecursion: PSynLogCurrentIdents; fRecursionCaller: PtrUInt; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; class function FamilyCreate: TSynLogFamily; procedure DoEnterLeave(aLevel: TSynLogInfo); procedure CreateLogWriter; virtual; procedure LogInternal(Level: TSynLogInfo; TextFmt: PWinAnsiChar; ................................................................................ - if you just need to access the log inside the method block, you may not need any ISynLog interface: ! procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self,'SQLFlush'); ! // do some stuff ! end; - if no Method name is supplied, it will use the caller address, and will write it as hexa and with full unit and symbol name, if the debugging information is available (i.e. if TSynMapFile retrieved the .map content): ! procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self); ! // do some stuff ! end; - note that supplying a method name is faster than using the .map content: if you want accurate profiling, it's better to use a method name or not to use a .map file - Enter() will write the class name (and the unit name for classes with published properties, if TSynLogFamily.WithUnitName is true) for both enter (+) and leave (-) events: $ 20110325 19325801 + MyDBUnit.TMyDB(004E11F4).SQLExecute $ 20110325 19325801 info SQL=SELECT * FROM Table; $ 20110325 19325801 - MyDBUnit.TMyDB(004E11F4).SQLExecute } class function Enter(aInstance: TObject=nil; aMethodName: PUTF8Char=nil): ISynLog; overload; {{ to be called and assigned to a ISynLog interface at the beginning of a method - this is the main method to be called within a class method: ! class function TMyDB.SQLValidate(const SQL: RawUTF8): boolean; ! var ILog: ISynLog; ! begin ! ILog := TSynLogDB.Enter(self,'SQLValidate'); ................................................................................ - if you just need to access the log inside the method block, you may not need any ISynLog interface: ! class procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self,'SQLFlush'); ! // do some stuff ! end; - if no Method name is supplied, it will use the caller address, and will write it as hexa and with full unit and symbol name, if the debugging information is available (i.e. if TSynMapFile retrieved the .map content): ! class procedure TMyDB.SQLFlush; ! begin ! TSynLogDB.Enter(self); ! // do some stuff ! end; - note that supplying a method name is faster than using the .map content: if you want accurate profiling, it's better to use a method name or not to use a .map file - Enter() will write the class name (and the unit name for classes with published properties, if TSynLogFamily.WithUnitName is true) for both enter (+) and leave (-) events: $ 20110325 19325801 + MyDBUnit.TMyDB.SQLValidate $ 20110325 19325801 info SQL=SELECT * FROM Table returned 1; $ 20110325 19325801 - MyDBUnit.TMyDB.SQLValidate } class function Enter(aClassType: TClass; aMethodName: PUTF8Char=nil): ISynLog; overload; /// retrieve the current instance of this TSynLog class // - to be used for direct logging, without any Enter/Leave: // ! TSynLogDB.Add.Log(llError,'The % statement didn't work',[SQL]); // - is just a wrapper around Family.SynLog - the same code will work: // ! TSynLogDB.Family.SynLog.Log(llError,'The % statement didn't work',[SQL]); class function Add: TSynLog; {$ifdef HASINLINE}inline;{$endif} ................................................................................ // class name and address (to be used e.g. if you didn't call TSynLog.Enter() // method first) - for instance // ! TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector); // will append this line to the log: // $ 0000000000002DB9 debug TObjectList(00425E68) GarbageCollector // - if Instance is set and Text is '', will behave the same as // Log(Level,Instance), i.e. write the Instance as JSON content procedure Log(Level: TSynLogInfo; const Text: RawUTF8; aInstance: TObject=nil); overload; /// call this method to add the content of an object to the log at a // specified level // - this default implementation will just write the class name and its hexa // pointer value, and handle TList, TCollections and TStrings - for instance: // ! TSynLog.Add.Log(sllDebug,GarbageCollector); // will append this line to the log: ................................................................................ /// call this method to add the content of most low-level types to the log // at a specified level // - this overriden implementation will write the value content, // written as human readable JSON: handle dynamic arrays and enumerations // - TSQLLog from SQLite3Commons unit will be able to write // TObject/TSQLRecord and sets content as JSON procedure Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); overload; /// call this method to add the caller address to the log at the specified level // - if the debugging info is available from TSynMapFile, will log the // unit name, associated symbol and source code line procedure Log(Level: TSynLogInfo); overload; published /// the associated logging family property GenericFamily: TSynLogFamily read fFamily; /// the associated file name containing the log // - this is accurate only with the default implementation of the class: // any child may override it with a custom logging mechanism property FileName: TFileName read fFileName; ................................................................................ var L2: integer; begin result := ''; // somewhat faster if result is freed before any SetLength() if L=0 then L := StrLen(P); if L=0 then exit; SetString(result,nil,L); // maximum posible unicode size (if all <#128) L2 := UTF8ToWideChar(pointer(result),P,L) shr 1; if L2<>L then SetLength(result,L2); end; function WinAnsiToUnicodeString(WinAnsi: PAnsiChar; WinAnsiLen: integer): UnicodeString; var i: integer; begin SetString(result,nil,WinAnsiLen); for i := 0 to WinAnsiLen-1 do PWordArray(result)[i] := WinAnsiTableA[WinAnsi[i]]; // very fast conversion end; {$endif} {$ifdef UNICODE} function Ansi7ToString(const Text: RawByteString): string; var i: integer; begin SetString(result,nil,length(Text)); for i := 0 to length(Text)-1 do PWordArray(result)[i] := PByteArray(Text)[i]; // no conversion for 7 bit Ansi end; {$else} function Ansi7ToString(const Text: RawByteString): string; begin result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign end; {$endif} {$ifdef UNICODE} function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string; var i: integer; begin SetString(result,nil,Len); for i := 0 to Len-1 do PWordArray(result)[i] := PByteArray(Text)[i]; // no conversion for 7 bit Ansi end; {$else} function Ansi7ToString(Text: PWinAnsiChar; Len: integer): string; begin SetString(result,PAnsiChar(Text),Len); end; {$endif} {$ifdef UNICODE} function StringToAnsi7(const Text: string): RawByteString; var i: integer; begin SetString(result,nil,length(Text)); for i := 0 to length(Text)-1 do PByteArray(result)[i] := PWordArray(Text)[i]; // no conversion for 7 bit Ansi end; {$else} function StringToAnsi7(const Text: string): RawByteString; begin result := Text; // if we are SURE this text is 7 bit Ansi -> direct assign ................................................................................ SetString(result,PAnsiChar(pointer(S)),length(S)*2+1); // +1 for last wide #0 end; {$else} function StringToRawUnicode(const S: string): RawUnicode; var i, L: integer; begin L := length(S); SetString(result,nil,L*2+1); // +1 for last wide #0 if GetACP<>1252 then begin // low-level MBCS RTL function including last widechar #0 SetLength(result,MultiByteToWideChar(GetACP, 0, Pointer(s), L, pointer(result), L)); end else // fast WinAnsi conversion for i := 0 to L do // includes S[L]=#0 -> last widechar #0 PWordArray(result)[i] := WinAnsiTable[PByteArray(S)[i]]; ................................................................................ // this Pos() is seldom used, it was decided to only define it under // Delphi 2009/2010/XE (which expect such a RawUTF8 specific overloaded version) function Pos(const substr, str: RawUTF8): Integer; overload; begin Result := PosEx(substr, str, 1); end; function IntToString(Value: integer): string; var tmp: array[0..15] of AnsiChar; P: PAnsiChar; begin P := StrInt32(@tmp[15],Value); result := Ansi7ToString(P,@tmp[15]-P); end; function IntToString(Value: cardinal): string; var tmp: array[0..15] of AnsiChar; P: PAnsiChar; begin P := StrUInt32(@tmp[15],Value); result := Ansi7ToString(P,@tmp[15]-P); end; function IntToString(Value: Int64): string; var tmp: array[0..31] of AnsiChar; P: PAnsiChar; begin P := StrInt64(@tmp[31],Value); result := Ansi7ToString(P,@tmp[31]-P); end; {$else UNICODE} function IntToString(Value: integer): string; {$ifdef PUREPASCAL} var tmp: array[0..15] of AnsiChar; P: PAnsiChar; begin P := StrInt32(@tmp[15],Value); SetString(result,P,@tmp[15]-P); end; {$else} asm jmp Int32ToUTF8 end; {$endif} function IntToString(Value: cardinal): string; var tmp: array[0..15] of AnsiChar; P: PAnsiChar; begin P := StrUInt32(@tmp[15],Value); SetString(result,P,@tmp[15]-P); end; function IntToString(Value: Int64): string; var tmp: array[0..31] of AnsiChar; P: PAnsiChar; begin P := StrInt64(@tmp[31],Value); SetString(result,P,@tmp[31]-P); end; {$endif UNICODE} {$ifndef ISDELPHI2007ANDUP} /// faster implementation of Move() for Delphi versions with no FastCode inside procedure Move(const Source; var Dest; Count: Integer); asm // eax=source edx=dest ecx=count ................................................................................ if byte(U^) and $80<>0 then break; // 7 bits char check only until false; {$endif} // find beginning of next word U := FindNextUTF8WordBegin(U); until U=nil; end; function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: integer): boolean; var B,C: byte; i: integer; begin result := false; // return false if any invalid char if Pointer(ConvertHexToBin)=nil then InitConvertHexToBin; inc(Bin,BinBytes-1); for i := 1 to BinBytes do begin B := ConvertHexToBin[Ord(Hex^)]; inc(Hex); if B>15 then exit; C := ConvertHexToBin[Ord(Hex^)]; Inc(Hex); if C>15 then exit; Bin^ := B shl 4+C; Dec(Bin); end; result := true; // correct content in Hex end; function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: Integer): boolean; var I: Integer; B,C: byte; begin result := false; // return false if any invalid char if Pointer(ConvertHexToBin)=nil then ................................................................................ var F: THandle; Size: integer; begin result := ''; if FileName='' then exit; F := FileOpen(FileName,fmOpenRead or fmShareDenyNone); if PtrInt(F)>=0 then begin {$ifdef LINUX} Size := FileSeek(F,0,soFromEnd); FileSeek(F,0,soFromBeginning); {$else} Size := GetFileSize(F,nil); {$endif} SetLength(result,Size); ................................................................................ function FileFromString(const Content: RawByteString; const FileName: TFileName; FlushOnDisk: boolean=false): boolean; var F: THandle; L: integer; begin result := false; F := FileCreate(FileName); if PtrInt(F)<0 then exit; if pointer(Content)<>nil then L := FileWrite(F,pointer(Content)^,length(Content)) else L := 0; result := (L=length(Content)); {$ifdef MSWINDOWS} if FlushOnDisk then ................................................................................ SetString(result,tmp,15); end; end; function DateToIso8601(Date: TDateTime; Expanded: boolean): RawUTF8; // we use YYYYMMDDTdate format begin SetString(result,nil,8+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} DateToIso8601PChar(Date,pointer(result),Expanded); end; /// basic Date conversion into ISO-8601 // - use 'YYYYMMDD' format if not Expanded // - use 'YYYY-MM-DD' format if Expanded function DateToIso8601(Y,M,D: cardinal; Expanded: boolean): RawUTF8; overload; begin SetString(result,nil,8+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} DateToIso8601PChar(pointer(result),Expanded,Y,M,D); end; function TimeToIso8601(Time: TDateTime; Expanded: boolean): RawUTF8; // we use Thhmmss format begin SetString(result,nil,7+2*integer(Expanded)); {$ifdef UNICODE2} // not needed: SetLength() did already set the codepage PWord(PtrUInt(result)-12)^ := CP_UTF8; // use only SetLength() -> force set code page {$endif} TimeToIso8601PChar(Time,pointer(result),Expanded); end; function DateTimeToIso8601Text(DT: TDateTime): RawUTF8; ................................................................................ {$ifdef MSWINDOWS} Now: TSystemTime; {$else} D: TDateTime; {$endif} begin if aFileName='' then exit; F := FileOpen(aFileName,fmOpenWrite); if PtrInt(F)<0 then begin F := FileCreate(aFileName); if PtrInt(F)<0 then exit; end; // append to end of file if FileSeek(F,0,soFromEnd)>MAXLOGSIZE then begin // rotate log file if too big FileClose(F); Old := aFileName+'.bak'; // '.log.bak' DeleteFile(Old); // rotate once RenameFile(aFileName,Old); F := FileCreate(aFileName); if PtrInt(F)<0 then exit; end; PWord(@Date)^ := 13+10 shl 8; // first go to next line {$ifdef MSWINDOWS} GetLocalTime(Now); // windows dedicated function DateToIso8601PChar(@Date[3],true,Now.wYear,Now.wMonth,Now.wDay); TimeToIso8601PChar(@Date[13],true,Now.wHour,Now.wMinute,Now.wSecond,' '); ................................................................................ BuildYear := SystemTime.wYear; end; end; finally Freemem(Pt); end; end; Main := IntToString(Major)+'.'+IntToString(Minor); Detailed := Main+ '.'+IntToString(Release)+'.'+IntToString(Build); if BuildDateTime=0 then // get build date from file age BuildDateTime := FileAgeToDateTime(FileName); end; function TFileVersion.Version32: integer; begin result := Major shl 16+Minor shl 8+Release; ................................................................................ exit; // avoid GPF if void SetCount(result+1); P := pointer(PtrUInt(Value^)+PtrUInt(result)*ElemSize); if ElemType=nil then move(Elem,P^,ElemSize) else CopyArray(P,@Elem,ElemType,1); end; function TDynArray.New: integer; begin result := Count; if Value=nil then exit; // avoid GPF if void SetCount(result+1); end; procedure TDynArray.Insert(Index: Integer; const Elem); var n: integer; P: PByteArray; begin if Value=nil then exit; // avoid GPF if void ................................................................................ result := Hasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^*2); end; procedure TDynArrayHashed.Init(aTypeInfo: pointer; var aValue; aHashElement: TDynArrayHashOne=nil; aCompare: TDynArraySortCompare=nil; aHasher: THasher=nil; aCountPointer: PInteger=nil); var FieldTable: PFieldTable; label RecH, RecC; begin inherited Init(aTypeInfo,aValue,aCountPointer); if @aHasher=nil then begin if @DefaultHasher=nil then DefaultHasher := @kr32; fHasher := DefaultHasher; end else ................................................................................ case PByte(ElemType)^ of tkLString: aHashElement := HashAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aHashElement := HashUnicodeString; tkRecord: begin FieldTable := ElemType; RecH: inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count>0 then with FieldTable^.Fields[0] do if Offset=0 then case TypeInfo^^.Kind of tkLString: aHashElement := HashAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aHashElement := HashUnicodeString; tkRecord: begin FieldTable := pointer(TypeInfo^); goto RecH; end; end; end; end; fHashElement := aHashElement; if @aCompare=nil then begin aCompare := SORTFIRSTFIELD[TypeInfoToKnown(aTypeInfo)]; if @aCompare=nil then // will match default HashOne algo, i.e. hash first field of record if (ElemType<>nil) and (PByte(ElemType)^=tkRecord) then begin FieldTable := ElemType; RecC: inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count=0 then case ElemSize of 1: aCompare := @SortDynArrayByte; 2: aCompare := @SortDynArrayWord; 4: aCompare := @SortDynArrayCardinal; 8: aCompare := @SortDynArrayInt64; end else ................................................................................ 8: aCompare := @SortDynArrayInt64; end else case TypeInfo^^.Kind of tkLString: aCompare := @SortDynArrayAnsiString; tkWString{$ifdef UNICODE}, tkUString{$endif}: aCompare := @SortDynArrayUnicodeString; tkRecord: begin FieldTable := pointer(TypeInfo^); goto RecC; end; end; end; end; fCompare := aCompare; ReHash; end; ................................................................................ end; until false; result := -1; // mark not found end; function TDynArrayHashed.HashOne(const Elem): cardinal; var FieldTable: PFieldTable; label Bin, LStr, WStr, Rec; begin if @fHashElement<>nil then result := fHashElement(Elem,fHasher) else if ElemType=nil then goto Bin else case PByte(ElemType)^ of tkLString: ................................................................................ result := fHasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^); tkWString{$ifdef UNICODE}, tkUString{$endif}: WStr: if PtrUInt(Elem)=0 then result := 0 else result := fHasher(0,Pointer(PtrUInt(Elem)),PInteger(PtrUInt(Elem)-4)^*2); tkRecord: begin FieldTable := ElemType; Rec: inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0])); if FieldTable^.Count=0 then // only binary content -> hash full content Bin: case ElemSize of 1: result := byte(Elem); 2: result := word(Elem); 4: result := cardinal(Elem); 8: result := Int64Rec(Elem).Lo xor Int64Rec(Elem).Hi; ................................................................................ if Offset<>0 then // hash whole starting binary content of record result := fHasher(0,@Elem,Offset) else // hash first string field of record case TypeInfo^^.Kind of tkLString: goto LStr; tkWString{$ifdef UNICODE}, tkUString{$endif}: goto WStr; tkRecord: begin FieldTable := pointer(TypeInfo^); goto Rec; end; else result := 0; // unhandled type end; end; else result := 0; end; if result=0 then result := 1; // never return 0 (indicates a free position in fHashs[]) ................................................................................ fOwner := Owner; end; function TSynTestCase.RandomString(CharCount: Integer): RawByteString; var V: cardinal; P: PAnsiChar; begin SetString(result,nil,CharCount); P := pointer(Result); while CharCount>0 do begin if CharCount>5 then begin V := Random(maxInt); // fast: one random compute per 5 chars P[0] := AnsiChar(32+V and 127); V := V shr 7; P[1] := AnsiChar(32+V and 127); V := V shr 7; P[2] := AnsiChar(32+V and 127); V := V shr 7; ................................................................................ Check(Cities[i].Name=''); // ForAddind = not added Cities[i] := City; end; Check(ACities.Count=3); Check(City.Name='Iasi'); Check(ACities.FindHashed(City)>=0); for i := 1 to 2000 do begin City.Name := IntToString(i); City.Latitude := i*3.14; City.Longitude := i*6.13; Check(ACities.FindHashedAndUpdate(City,true)=i+2,'multiple ReHash'); Check(ACities.FindHashed(City)=i+2); end; ACities.Capacity := CITIES_MAX+3; // make it as fast as possible for i := 2001 to CITIES_MAX do begin City.Name := IntToString(i); City.Latitude := i*3.14; City.Longitude := i*6.13; Check(ACities.FindHashedAndUpdate(City,true)=i+2,'use Capacity: no ReHash'); Check(ACities.FindHashed(City.Name)=i+2); end; for i := 1 to CITIES_MAX do begin N := IntToString(i); j := ACities.FindHashed(N); Check(j=i+2,'hashing with string not City.Name'); Check(Cities[j].Name=N); CheckSame(Cities[j].Latitude,i*3.14); CheckSame(Cities[j].Longitude,i*6.13); end; end; ................................................................................ JSON_BASE64_MAGIC_UTF8: RawUTF8; procedure Fill(var F: TFileVersion; i: integer); begin F.Major := i; F.Minor := i+1; F.Release := i+2; F.Build := i+3; F.Main := IntToString(i+1000); F.Detailed := IntToString(2000-i); F.BuildDateTime := 36215.12; F.BuildYear := i+2011; end; begin W := TTextWriter.CreateOwnedStream; // validate TIntegerDynArray AIP.Init(TypeInfo(TIntegerDynArray),AI); ................................................................................ Check(RecordEquals(F,AF[100],TypeInfo(TFileVersion))); for i := 0 to 1000 do with AF[i] do begin Check(Major=i); Check(Minor=i+1); Check(Release=i+2); Check(Build=i+3); Check(Main=IntToString(i+1000)); Check(Detailed=IntToString(2000-i)); CheckSame(BuildDateTime,36215.12); Check(BuildYear=i+2011); end; for i := 0 to 1000 do begin Fill(F,i); Check(AFP.IndexOf(F)=i); end; ................................................................................ {$endif} for i := 1 to 10000 do begin j := Random(maxInt)-Random(maxInt); str(j,a); s := RawUTF8(a); Check(kr32(0,pointer(s),length(s))=kr32pas(pointer(s),length(s))); u := string(a); Check(SysUtils.IntToStr(j)=u); Check(Int32ToUtf8(j)=s); Check(format('%d',[j])=u); Check(GetInteger(pointer(s))=j); Check(FormatUTF8('%',[j])=s); k := Int64(j)*Random(MaxInt); str(k,a); s := RawUTF8(a); u := string(a); Check(SysUtils.IntToStr(k)=u); Check(Int64ToUtf8(k)=s); Check(format('%d',[k])=u); Check(FormatUTF8('%',[k])=s); err := 1; l := GetInt64(pointer(s),err); Check((err=0)and(l=k)); str(j,a); Check(SysUtils.IntToStr(j)=string(a)); Check(format('%d',[j])=string(a)); Check(format('%.8x',[j])=IntToHex(j,8)); d := Random*1E-17-Random*1E-9; str(d,a); s := RawUTF8(a); e := GetExtended(Pointer(s),err); Check(SameValue(e,d)); // test str() ................................................................................ Check(T.AddField('double',tftDouble)<>nil); Check(T.AddField('varint',tftVarUInt32)<>nil); Check(T.AddField('text',tftUTF8,[tfoUnique])<>nil); Check(T.AddField('ansi',tftWinAnsi,[])<>nil); Check(T.AddField('currency',tftCurrency)<>nil); Test; FN := ChangeFileExt(paramstr(0),'.syntable'); DeleteFile(FN); f := FileCreate(FN); // manual storage of TSynTable header W.AssignToFile(f); T.SaveTo(W); W.Flush; FileClose(f); T.Free; f := FileOpen(FN,fmOpenRead); ................................................................................ WriteVarUInt32(ValuesCount); if ValuesCount=0 then exit; PI := pointer(Values); PByteArray(fBuf)^[fPos] := ord(DataLayout); inc(fPos); inc(fTotalWritten); if DataLayout in [wkOffsetU, wkOffsetI] then begin pos := fPos; fPos := PtrUInt(ToVarUInt32(PI^[0],@PByteArray(fBuf)^[fPos]))-PtrUInt(fBuf); diff := PI^[1]-PI^[0]; inc(PtrUInt(PI),4); dec(ValuesCount); if ValuesCount=0 then begin inc(fTotalWritten,PtrUInt(fPos-pos)); ................................................................................ wkUInt32: begin n := (fBufLen-fPos)shr 2; if ValuesCount<n then n := ValuesCount; Move(PI^,P^,n*4); inc(P,n*4); end; wkVarInt32, wkVarUInt32, wkOffsetU, wkOffsetI: begin PBeg := PAnsiChar(P); // leave space for chunk size inc(P,4); n := ValuesCount; case DataLayout of wkVarInt32: for i := 0 to ValuesCount-1 do begin P := ToVarInt32(PI^[i],P); ................................................................................ for i := 0 to ValuesCount-1 do begin P := ToVarUInt32(PI^[i],P); if PtrUInt(P)>=PtrUInt(PEnd) then begin n := i+1; break; // avoid buffer overflow end; end; wkOffsetU: for i := 0 to ValuesCount-1 do begin P := ToVarUInt32(PI^[i]-PI^[i-1],P); if PtrUInt(P)>=PtrUInt(PEnd) then begin n := i+1; break; // avoid buffer overflow end; end; wkOffsetI: for i := 0 to ValuesCount-1 do begin P := ToVarInt32(PI^[i]-PI^[i-1],P); if PtrUInt(P)>=PtrUInt(PEnd) then begin n := i+1; break; // avoid buffer overflow end; end; end; PInteger(PBeg)^ := PAnsiChar(P)-PBeg-4; // format: Isize+varUInt32s end; wkSorted: begin PBeg := PAnsiChar(P)+4; // leave space for chunk size P := PByte(CleverStoreInteger(pointer(PI),PBeg,PEnd,ValuesCount,n)); ................................................................................ end; end; procedure TFileBufferReader.ErrorInvalidContent; begin raise Exception.Create('TFileBufferReader: invalid content'); end; procedure TFileBufferReader.OpenFrom(aBuffer: pointer; aBufferSize: cardinal); begin fCurrentPos := 0; fBuf := aBuffer; fBufSize := aBufferSize; fFileSize := aBufferSize; fMap := 0; end; procedure TFileBufferReader.Open(aFile: THandle); begin fCurrentPos := 0; fBuf := nil; fMap := 0; fFile := aFile; ................................................................................ end; end; function TFileBufferReader.Read(Data: pointer; DataLen: integer): integer; var len: integer; begin if DataLen>0 then if fBuf<>nil then begin // file up to 2 GB: use fast memory map len := fBufSize-fCurrentPos; if len>DataLen then len := DataLen; move(fBuf[fCurrentPos],Data^,len); inc(fCurrentPos,len); result := len; ................................................................................ ErrorInvalidContent; PEnd := pointer(PtrUInt(P)+PtrUInt(len)); end; function TFileBufferReader.ReadPointer(DataLen: PtrUInt; var aTempData: RawByteString): pointer; begin if fBuf=nil then begin // read from file if DataLen>PtrUInt(Length(aTempData)) then begin aTempData := ''; // so no move() call in SetLength() below SetLength(aTempData,DataLen); end; if PtrUInt(FileRead(fFile,pointer(aTempData)^,DataLen))<>DataLen then result := nil else // invalid content ................................................................................ {$ifndef LVCL} function TFileBufferReader.ReadStream: TCustomMemoryStream; var DataLen: PtrUInt; begin DataLen := ReadVarUInt32; if DataLen<>0 then if fBuf=nil then begin // use temporary TMemoryStream only if not in memory map buffer result := TMemoryStream.Create; result.Size := DataLen; if PtrUInt(FileRead(fFile,result.Memory^,DataLen))<>DataLen then FreeAndNil(result); // invalid content end else if DataLen+fCurrentPos>fBufSize then ................................................................................ // DataLen=0 -> invalid content result := nil; end; {$endif} function TFileBufferReader.ReadByte: PtrUInt; begin if fBuf<>nil then if fCurrentPos>=fBufSize then // invalid request result := 0 else begin // read one byte from current memory map result := ord(fBuf[fCurrentPos]); inc(fCurrentPos); end else begin // read from file if >= 2 GB (slow, but works) result := 0; if FileRead(fFile,result,1)<>1 then result := 0; end; end; function TFileBufferReader.ReadCardinal: cardinal; begin if fBuf<>nil then if fCurrentPos+3>=fBufSize then // invalid request result := 0 else begin // read one byte from current memory map result := PCardinal(fBuf+fCurrentPos)^; inc(fCurrentPos,4); end else begin // read from file if >= 2 GB (slow, but works) result := 0; if FileRead(fFile,result,4)<>4 then result := 0; end; end; function TFileBufferReader.ReadVarUInt32: PtrUInt; var c, n: PtrUInt; begin result := ReadByte; if result>$7f then begin n := 0; ................................................................................ if result=0 then exit; count := result; if count>length(Values) then // only set length is not big enough SetLength(Values,count); PI := pointer(Values); DataLayout := TFileBufferWriterKind(ReadByte); if DataLayout in [wkOffsetU, wkOffsetI] then begin PI^ := ReadVarUInt32; dec(count); diff := ReadVarUInt32; if diff<>0 then begin for i := 0 to count-1 do PIA^[i+1] := PIA^[i]+diff; exit; ................................................................................ inc(PI); end; wkSorted: begin n := CleverReadInteger(pointer(P),pointer(PEnd),PI); dec(count,n); inc(PtrUInt(PI),n*4); end; wkOffsetU: begin while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin PIA^[1] := PIA^[0]+integer(FromVarUInt32(P)); dec(count); inc(PI); end; if count<=0 then inc(PI); // make sure PI=@Values[result] end; wkOffsetI: begin while (count>0) and (PtrUInt(P)<PtrUInt(PEnd)) do begin PIA^[1] := PIA^[0]+FromVarInt32(P); dec(count); inc(PI); end; if count<=0 then inc(PI); // make sure PI=@Values[result] end; else ................................................................................ assert((count=0)and(PI=@Values[result])); end; function TFileBufferReader.ReadRawUTF8List(List: TRawUTF8List): boolean; var i: integer; StoreObjectsAsVarUInt32: Boolean; begin if (fBuf<>nil) and (List<>nil) then with List do begin BeginUpdate; try Capacity := 0; // finalize both fObjects[] and fList[] fCount := ReadVarRawUTF8DynArray(List.fList); result := true; if fCount=0 then ................................................................................ end; {$ifndef CPU64} function TFileBufferReader.Seek(Offset: Int64): boolean; begin if (Offset<0) or (Offset>fFileSize) then result := False else if fBuf=nil then result := FileSeek64(fFile,Offset,soFromBeginning)=Offset else begin fCurrentPos := Int64Rec(Offset).Lo; result := true; end; end; {$endif CPU64} function TFileBufferReader.Seek(Offset: PtrInt): boolean; begin // we don't need to handle fMap=0 here if fBuf=nil then Result := FileSeek(fFile,Offset,0)=Offset else if (fBuf<>nil) and (PtrUInt(Offset)<PPtrUInt(@fFileSize)^) then begin fCurrentPos := Offset; result := true; end else result := false; end; ................................................................................ inc(P,Len); inc(TotalLen,Len); Lens[F] := Len; end else inc(TotalLen,fDefaultFieldLength); // create new record content P := RecordBuffer; SetString(Result,nil,TotalLen); Dest := pointer(Result); for F := 0 to fField.Count-1 do with TSynTableFieldProperties(fField.List[F]) do if F in AvailableFields then begin Len := Lens[F]; move(P^,Dest^,Len); inc(P,Len); ................................................................................ Head: integer; begin if FieldType<>tftBlobInternal then result := '' else if (Value=nil) or (ValueLen=0) then result := #0 else begin // inlined ToSBFStr() code Head := PAnsiChar(ToVarUInt32(ValueLen,@tmp))-tmp; SetString(Result,nil,ValueLen+Head); Move(tmp,PByteArray(Result)[0],Head); Move(Value^,PByteArray(Result)[Head],ValueLen); end; end; function TSynTableFieldProperties.SBFFloat(const Value: Double): TSBFString; begin ................................................................................ function TSynTableData.ValidateSBFValue(RecordIndex: integer): string; begin CheckVTableInitialized; Result := VTable.Validate(Pointer(VValue),RecordIndex); end; { TSynMapFile } var ExeMapFile: TSynMapFile = nil; const MAB_MAGIC: cardinal = $A5A5A5A5; function MatchPattern(P,PEnd,Up: PUTF8Char): boolean; begin result := false; repeat while P^=' ' do inc(P); while NormToUpperAnsi7[P^]=Up^ do begin inc(P); if P>PEnd then exit; inc(Up); if (Up^=' ') and (P^=' ') then begin // ignore multiple spaces in P^ while (P<PEnd) and (P^=' ') do inc(P); inc(Up); end; end; if Up^=#0 then // all chars of Up^ found in P^ break else if Up^<>' ' then // P^ and Up^ didn't match exit; inc(Up); until false; result := true; end; procedure ReadSymbol(var R: TFileBufferReader; const A: TDynArray); var i, n, L: integer; S: PSynMapSymbol; Addr: cardinal; P: PByte; begin n := R.ReadVarUInt32; A.Count := n; if n=0 then exit; P := @R.fBuf[R.fCurrentPos]; S := A.Value^; for i := 0 to n-1 do begin L := FromVarUInt32(P); // inlined R.Read(S^.Name) SetString(S^.Name,PAnsiChar(P),L); inc(P,L); inc(PtrUInt(S),A.ElemSize); end; S := A.Value^; Addr := FromVarUInt32(P); S^.Start := Addr; for i := 1 to n-1 do begin inc(Addr,FromVarUInt32(P)); S^.Stop := Addr-1; inc(PtrUInt(S),A.ElemSize); S^.Start := Addr; end; S^.Stop := Addr+FromVarUInt32(P); R.fCurrentPos := PtrUInt(P)-PtrUInt(R.fBuf); end; constructor TSynMapFile.Create(const aExeName: TFileName=''; MabCreate: boolean=true); procedure LoadMap; var P, PEnd: PUTF8Char; procedure NextLine; begin while (P<PEnd) and (P^>=' ') do inc(P); if (P<PEnd) and (P^=#13) then inc(P); if (P<PEnd) and (P^=#10) then inc(P); end; function GetCode(var Ptr: cardinal): boolean; begin while (P<PEnd) and (P^=' ') do inc(P); result := false; if (P+10<PEnd) and (PInteger(P)^=ord('0')+ord('0')shl 8+ord('0')shl 16+ord('1')shl 24) and (P[4]=':') then begin if not HexDisplayToBin(PAnsiChar(P)+5,@Ptr,sizeof(Ptr)) then exit; while (P<PEnd) and (P^>' ') do inc(P); while (P<PEnd) and (P^=' ') do inc(P); if P<PEnd then result := true; end; end; procedure ReadSegments; var Beg: PAnsiChar; U: TSynMapUnit; begin NextLine; NextLine; while (P<PEnd) and (P^<' ') do inc(P); while (P+10<PEnd) and (P^>=' ') do begin if GetCode(U.Symbol.Start) and HexDisplayToBin(PAnsiChar(P),@U.Symbol.Stop,4) then begin while PWord(P)^<>ord('M')+ord('=')shl 8 do if P+10>PEnd then exit else inc(P); Beg := pointer(P+2); while (P<PEnd) and (P^>' ') do inc(P); SetString(U.Symbol.Name,Beg,P-Beg); inc(U.Symbol.Stop,U.Symbol.Start); if U.Symbol.Start<>0 then dec(U.Symbol.Stop); // fix incoherency in .map for 1st segment if U.Symbol.Name<>'' then fUnits.FindHashedAndUpdate(U,true); // true for adding end; NextLine; end; end; procedure ReadSymbols; var Beg: PAnsiChar; Sym: TSynMapSymbol; begin NextLine; NextLine; while (P+10<PEnd) and (P^>=' ') do begin if GetCode(Sym.Start) then begin while (P<PEnd) and (P^=' ') do inc(P); Beg := pointer(P); while (P<PEnd) and (P^>' ') do inc(P); SetString(Sym.Name,Beg,P-Beg); if Sym.Name<>'' then fSymbols.Add(Sym); end; NextLine; end; end; procedure ReadLines; var Beg: PAnsiChar; i, Count: integer; aName: RawUTF8; added: boolean; begin inc(P,17); Beg := pointer(P); while P^<>'(' do if P=PEnd then exit else inc(P); SetString(aName,Beg,P-Beg); { inc(P); Beg := pointer(P); // we don't need the filename by now while P^<>')' do if P=PEnd then exit else inc(P); SetString(aFileName,Beg,P-Beg); } if aName='' then exit; i := fUnits.FindHashedForAdding(aName,added); if added then fUnit[i].Symbol.Name := aName; // should not occur, but who knows... NextLine; NextLine; Count := 0; with fUnit[i] do begin // FileName := aFileName; while (P+10<PEnd) and (P^>=' ') do begin while (P<PEnd) and (P^=' ') do inc(P); repeat if Count=length(Line) then begin SetLength(Line,Count+256); SetLength(Addr,Count+256); end; Line[Count] := GetNextItemCardinal(P,' '); if not GetCode(cardinal(Addr[Count])) then break; inc(Count); until (P>=PEnd) or (P^<' '); NextLine; end; SetLength(Line,Count); SetLength(Addr,Count); end; end; var i, s,u: integer; begin // LoadMap fSymbols.Capacity := 5000; with TMemoryStream.Create do try LoadFromFile(fMapFile); P := Memory; PEnd := P+Size; while P<PEnd do if MatchPattern(P,PEnd,'DETAILED MAP OF SEGMENTS') then ReadSegments else if MatchPattern(P,PEnd,'ADDRESS PUBLICS BY VALUE') then ReadSymbols else if MatchPattern(P,PEnd,'LINE NUMBERS FOR') then ReadLines else NextLine; // now we should have read all .map content u := fUnits.Count; s := fSymbols.Count; for i := 1 to u-1 do assert(fUnit[i].Symbol.Start>fUnit[i-1].Symbol.Stop); for i := 0 to s-2 do fSymbol[i].Stop := fSymbol[i+1].Start-1; if (u>0) and (s>0) then fSymbol[s-1].Stop := fUnit[u-1].Symbol.Stop; finally Free; end; end; procedure LoadMab(const aMabFile: TFileName); var tmp: cardinal; F: THandle; R: TFileBufferReader; i: integer; begin fMapFile := aMabFile; F := FileOpen(fMapFile,fmOpenRead or fmShareDenyNone); if PtrInt(F)>=0 then try R.Open(F); R.Seek(R.FileSize-8); if R.ReadCardinal<>MAB_MAGIC then exit; R.Read(@tmp,4); if tmp>R.FileSize then exit; R.Seek(R.FileSize-tmp); if R.ReadCardinal<>MAB_MAGIC then exit; ReadSymbol(R,fSymbols); ReadSymbol(R,fUnits); for i := 0 to fUnits.Count-1 do with fUnit[i] do begin R.ReadVarUInt32Array(Line); R.ReadVarUInt32Array(Addr); end; if R.ReadCardinal=MAB_MAGIC then MabCreate := false else fUnits.Count := 0; // invalid content finally FileClose(F); end; end; var SymCount, UnitCount, i: integer; MabFile: TFileName; begin fSymbols.Init(TypeInfo(TSynMapSymbolDynArray),fSymbol,@SymCount); fUnits.Init(TypeInfo(TSynMapUnitDynArray),fUnit,nil,nil,nil,@UnitCount); // 1. search for a .map file in the .exe directory if aExeName='' then fMapFile := paramstr(0) else fMapFile := aExeName; fMapFile := ChangeFileExt(fMapFile,'.map'); MabFile := ChangeFileExt(fMapFile,'.mab'); if FileExists(fMapFile) and // no slow read of .map if faster-to-load .mab is available and accurate (FileAge(MabFile)<FileAge(fMapFile)) then LoadMap; // 2. search for a .mab file in the .exe directory if SymCount=0 then LoadMab(MabFile); // 3. search for an embedded compressed .mab file appended to the .exe if SymCount=0 then LoadMab(ChangeFileExt(fMapFile,'.exe')); // finalize symbols if SymCount>0 then begin for i := 1 to SymCount-1 do assert(fSymbol[i].Start>fSymbol[i-1].Stop); SetLength(fSymbol,SymCount); SetLength(fUnit,UnitCount); fSymbols.fCountP := nil; fUnits.fCountP := nil; if MabCreate then SaveToFile(''); // if just created from .map -> create .mab file fHasDebugInfo := true; end; end; procedure WriteSymbol(var W: TFileBufferWriter; const A: TDynArray); var i, n: integer; Diff: cardinal; S: PSynMapSymbol; P: PByte; Beg: PtrUInt; begin n := A.Count; W.WriteVarUInt32(n); if n=0 then exit; S := A.Value^; for i := 0 to n-1 do begin W.Write(S^.Name); inc(PtrUInt(S),A.ElemSize); end; S := A.Value^; Diff := S^.Start; W.WriteVarUInt32(Diff); if W.fPos+n*5>W.fBufLen then W.Flush; with W do if fPos+n*5>fBufLen then raise Exception.CreateFmt('too big %s',[PDynArrayTypeInfo(A.TypeInfo).Name]) else P := @PByteArray(fBuf)^[fPos]; Beg := PtrUInt(P); for i := 1 to n-1 do begin inc(PtrUInt(S),A.ElemSize); P := ToVarUInt32(S^.Start-Diff,P); Diff := S^.Start; end; P := ToVarUInt32(S^.Stop-Diff,P); Beg := PtrUInt(P)-Beg; inc(W.fPos,Beg); inc(W.fTotalWritten,Beg); end; function TSynMapFile.SaveToFile(const aFileName: TFileName=''): TFileName; var W: TFileBufferWriter; F: THandle; i, Len: integer; begin if aFileName='' then result := ChangeFileExt(paramstr(0),'.mab') else result := aFileName; DeleteFile(result); F := FileCreate(result); if PtrInt(F)>=0 then try W.AssignToFile(F,1 shl 17); W.Write(@MAB_MAGIC,sizeof(MAB_MAGIC)); WriteSymbol(W,fSymbols); WriteSymbol(W,fUnits); for i := 0 to high(fUnit) do with fUnit[i] do begin W.WriteVarUInt32Array(Line,length(Line),wkOffsetI); // not always increasing W.WriteVarUInt32Array(Addr,length(Addr),wkOffsetU); // always increasing end; W.Write(@MAB_MAGIC,4); Len := W.TotalWritten+4; W.Write(@Len,4); W.Flush; finally FileClose(F); end; end; procedure TSynMapFile.SaveToExe(const aExeName: TFileName); var FN: TFileName; MS, MAB: TMemoryStream; Len, LenMAB: PtrUInt; P: PAnsiChar; begin if not FileExists(aExeName) then exit; FN := SaveToFile(ChangeFileExt(aExeName,'.mab')); try MS := TMemoryStream.Create; MAB := TMemoryStream.Create; try // load both files MAB.LoadFromFile(FN); LenMAB := MAB.Size; MS.LoadFromFile(aExeName); Len := MS.Size; if Len<16 then exit; P := MS.Memory; inc(P,Len); if PCardinal(P-8)^=MAB_MAGIC then // trim existing mab content dec(Len,PCardinal(P-4)^); // append mab content to exe MS.Size := Len+LenMAB; move(MAB.Memory^,PAnsiChar(MS.Memory)[Len],LenMAB); MS.SaveToFile(aExeName); finally MAB.Free; MS.Free; end; finally DeleteFile(FN); end; end; function TSynMapFile.FindSymbol(aAddr: cardinal): integer; var L,R: integer; begin R := high(fSymbol); L := 0; if (R>=0) and (aAddr>=fSymbol[0].Start) and (aAddr<=fSymbol[R].Stop) then repeat result := (L+R)shr 1; with fSymbol[result] do if aAddr<Start then R := result-1 else if aAddr>Stop then L := result+1 else exit; until L>R; result := -1; end; function TSynMapFile.FindUnit(aAddr: cardinal; out LineNumber: integer): integer; var L,R,n,max: integer; begin LineNumber := 0; R := high(fUnit); L := 0; if (R>=0) and (aAddr>=fUnit[0].Symbol.Start) and (aAddr<=fUnit[R].Symbol.Stop) then repeat result := (L+R) shr 1; with fUnit[result] do if aAddr<Symbol.Start then R := result-1 else if aAddr>Symbol.Stop then L := result+1 else begin // unit found -> search line number L := 0; max := high(Addr); R := max; if R>=0 then repeat n := (L+R) shr 1; if aAddr<cardinal(Addr[n]) then R := n-1 else if (n<max) and (aAddr>=cardinal(Addr[n+1])) then L := n+1 else begin LineNumber := Line[n]; exit; end; until L>R; exit; end; until L>R; result := -1; end; const /// Delphi linker starts the code section at this fixed offset CodeSection = $1000; class procedure TSynMapFile.Log(W: TTextWriter; Addr: PtrUInt); var u, s, Line: integer; begin if (W=nil) or (Addr=0) then exit; if ExeMapFile=nil then begin ExeMapFile := TSynMapFile.Create; GarbageCollector.Add(ExeMapFile); ExeMapFile.fGetModuleHandle := GetModuleHandle(nil)+CodeSection; end; W.AddPointer(Addr); W.Add(' '); with ExeMapFile do if HasDebugInfo then begin dec(Addr,fGetModuleHandle); s := FindSymbol(Addr); u := FindUnit(Addr,Line); if (s<0) and (u<0) then exit; if u>=0 then begin W.AddString(Units[u].Symbol.Name); if s>=0 then if Symbols[s].Name=Units[u].Symbol.Name then s := -1 else W.Add('.'); end; if s>=0 then W.AddString(Symbols[s].Name); W.Add(' '); if Line>0 then begin W.Add('('); W.Add(Line); W.Add(')',' '); end; end; end; { TSynLogFamily } /// if defined, will use AddVectoredExceptionHandler() API call // - this one does not produce accurate stack trace by now, and is supported // only since Windows XP // - so default method using RTLUnwindProc should be prefered {.$define WITH_VECTOREXCEPT} type TExceptProc = procedure(Obj: TObject; Addr: Pointer); var /// internal list of registered TSynLogFamily // - up to MAX_SYNLOGFAMILY+1 families may be defined SynLogFamily: TObjectList = nil; var SynLogExceptionEnabled: boolean = false; threadvar CurrentHandleExceptionSynLog: TSynLog; function GetHandleExceptionSynLog: TSynLog; var Index: ^TSynLogFileIndex; i: integer; ndx, n: cardinal; begin result := CurrentHandleExceptionSynLog; if (result<>nil) and result.fFamily.fHandleExceptions then exit; Index := @SynLogFileIndex; if SynLogFile=nil then begin // we are here is no previous log content was trigerred for i := 0 to SynLogFamily.Count-1 do with TSynLogFamily(SynLogFamily.List^[i]) do if fHandleExceptions then begin result := SynLog; exit; end; end else begin n := SynLogFile.Count; for i := 0 to high(Index^) do begin ndx := Index^[i]-1; if ndx<=n then begin result := TSynLog(SynLogFile.List^[ndx]); if result.fFamily.fHandleExceptions then exit; end; end; for i := 0 to n-1 do begin result := TSynLog(SynLogFile.List^[i]); if result.fFamily.fHandleExceptions then exit; end; end; result := nil; end; function check2(xret: PtrUInt): Boolean; var i: PtrUInt; begin result := true; for i := 2 to 7 do if PWord(xret-i)^ and $38FF=$10FF then exit; result := false; end; {$ifdef WITH_VECTOREXCEPT} type PExceptionRecord = ^TExceptionRecord; TExceptionRecord = packed record ExceptionCode : LongWord; ExceptionFlags : LongWord; OuterException : PExceptionRecord; ExceptionAddress : Pointer; ................................................................................ PExceptionInfo = ^TExceptionInfo; TExceptionInfo = packed record ExceptionRecord: PExceptionRecord; ContextRecord: pointer; end; GetExceptionClass = function(P: PExceptionRecord): ExceptClass; var AddVectoredExceptionHandler: function(FirstHandler: cardinal; VectoredHandler: pointer): PtrInt; stdcall; function SynLogVectoredHandler(ExceptionInfo : PExceptionInfo): PtrInt; stdcall; const EXCEPTION_CONTINUE_SEARCH = 0; var SynLog: TSynLog; Level: TSynLogInfo; E: ExceptClass; EAddr: pointer; LastError: DWORD; curr_stack: PPtrUInt; stack, max_stack, min_stack, depth: PtrUInt; begin if not SynLogExceptionEnabled then begin result := EXCEPTION_CONTINUE_SEARCH; // if called within function exit; end; LastError := GetLastError; // guess if was a Delphi or OS exception E := nil; EAddr := nil; Level := sllException; SynLog := GetHandleExceptionSynLog; if (SynLog<>nil) and (ExceptionInfo<>nil) and (ExceptionInfo^.ExceptionRecord<>nil) then with ExceptionInfo^.ExceptionRecord^ do if (ExceptObject<>nil) and ExceptObject.InheritsFrom(Exception) then begin // Delphi exception E := PPointer(ExceptObject)^; EAddr := ExceptAddr; end else begin // OS exception -> translate into a Delphi Exception class if Assigned(ExceptClsProc) then E := GetExceptionClass(ExceptClsProc)(ExceptionInfo^.ExceptionRecord); if E=nil then E := EExternalException; Level := sllExceptionOS; EAddr := ExceptionAddress; end; // log corresponding message if necessary if (E<>nil) and ((Level<>sllException) or (sllException in SynLog.fFamily.Level)) then begin SynLog.LogHeaderLock(Level); with SynLog.fWriter, ExceptionInfo^.ExceptionRecord^ do if Level=sllException then Add('% ("%") at ',[E,ExceptObject.Message]) else Add('% (%) at ',[E,pointer(ExceptionCode)]); TSynMapFile.Log(SynLog.fWriter,PtrUInt(EAddr)); depth := SynLog.fFamily.fStackTraceLevel; if depth>0 then begin SynLog.fWriter.AddShort('stack trace '); SynLogExceptionEnabled := false; // for IsBadCodePtr try asm mov eax,[ebp] // push ebp; mov ebp,esp done at begin level above mov curr_stack,eax mov eax,fs:[18h] mov ecx,dword ptr [eax+4] mov max_stack,ecx mov ecx,dword ptr [eax+8] mov min_stack,ecx end; while (PtrUInt(curr_stack)<max_stack) and (curr_stack^<>PtrUInt(EAddr)) do inc(curr_stack); while PtrUInt(curr_stack)<max_stack do begin stack := curr_stack^; if ((stack>max_stack) or (stack<min_stack)) and not IsBadReadPtr(pointer(stack-8),12) and ((pByte(stack-5)^=$E8) or check2(stack)) then begin TSynMapFile.Log(SynLog.fWriter,stack); dec(depth); if depth=0 then break; end; inc(curr_stack); end; finally SynLogExceptionEnabled := true; end; end; SynLog.fWriter.AddCR; SynLog.UnLock; end; SetLastError(LastError); // code above could have changed this result := EXCEPTION_CONTINUE_SEARCH; end; {$else WITH_VECTOREXCEPT} var oldUnWindProc: pointer; const cDelphiExcept = $0EEDFAE0; cDelphiException = $0EEDFADE; procedure LogExcept(stack: PPtrUInt; EAddr, ECode: PtrUInt; EObject: TObject); stdcall; var SynLog: TSynLog; Level: TSynLogInfo; E: ExceptClass; LastError: DWORD; st, max_stack, min_stack, depth: PtrUInt; begin if not SynLogExceptionEnabled then exit; SynLog := GetHandleExceptionSynLog; if SynLog=nil then exit; Level := sllError; LastError := GetLastError; if (ECode=cDelphiException) and (EObject<>nil) then begin if EObject.InheritsFrom(Exception) then E := PPointer(EObject)^ else E := EExternalException; if sllException in SynLog.fFamily.Level then Level := sllException; end else begin case ECode of STATUS_INTEGER_DIVIDE_BY_ZERO, STATUS_FLOAT_DIVIDE_BY_ZERO: E := EDivByZero; STATUS_ARRAY_BOUNDS_EXCEEDED: E := ERangeError; STATUS_INTEGER_OVERFLOW: E := EIntOverflow; STATUS_FLOAT_INEXACT_RESULT, STATUS_FLOAT_INVALID_OPERATION, STATUS_FLOAT_STACK_CHECK: E := EInvalidOp; STATUS_FLOAT_OVERFLOW: E := EOverflow; STATUS_FLOAT_UNDERFLOW, STATUS_FLOAT_DENORMAL_OPERAND: E := EUnderflow; STATUS_ACCESS_VIOLATION: E := EAccessViolation; STATUS_PRIVILEGED_INSTRUCTION: E := EPrivilege; STATUS_CONTROL_C_EXIT: E := EControlC; {$WARN SYMBOL_DEPRECATED OFF} STATUS_STACK_OVERFLOW: E := EStackOverflow; {$WARN SYMBOL_DEPRECATED ON} else E := EExternal; end; Level := sllExceptionOS; end; if (Level<>SllError) and (SynLog.fFamily.ExceptionIgnore.IndexOf(E)<0) then begin SynLog.LogHeaderLock(Level); if (Level=sllException) and (E<>EExternalException) then SynLog.fWriter.Add('% ("%") at ',[E,Exception(EObject).Message]) else SynLog.fWriter.Add('% (%) at ',[E,pointer(ECode)]); TSynMapFile.Log(SynLog.fWriter,EAddr); depth := SynLog.fFamily.fStackTraceLevel; if depth>0 then begin SynLog.fWriter.AddShort('stack trace '); SynLogExceptionEnabled := false; // for IsBadCodePtr try asm mov eax,fs:[18h] mov ecx,dword ptr [eax+4] mov max_stack,ecx mov ecx,dword ptr [eax+8] mov min_stack,ecx end; while PtrUInt(stack)<max_stack do begin st := stack^; if ((st>max_stack) or (st<min_stack)) and not IsBadReadPtr(pointer(st-8),12) and ((pByte(st-5)^=$E8) or check2(st)) then begin TSynMapFile.Log(SynLog.fWriter,st); dec(depth); if depth=0 then break; end; inc(stack); end; finally SynLogExceptionEnabled := true; end; end; SynLog.fWriter.AddCR; SynLog.UnLock; end; SetLastError(LastError); // code above could have changed this end; procedure SynRtlUnwind(TargetFrame, TargetIp, ExceptionRecord, ReturnValue: Pointer); stdcall; asm pushad cmp byte ptr SynLogExceptionEnabled,0 jz @oldproc mov eax,[ebp+8] or eax,eax jz @oldproc mov ebx,[eax+8] mov eax,[ebp+16] push dword ptr [eax+24] push dword ptr [eax] mov ecx,[eax] cmp ecx,cDelphiException je @delphi_exception cmp ecx,cDelphiExcept je @delphi_exception mov eax,[eax+12] push eax push ebx call LogExcept jmp @oldproc @delphi_exception: mov eax,[eax+20] push eax push ebx call LogExcept @oldproc: popad pop ebp jmp oldUnWindProc end; {$endif WITH_VECTOREXCEPT} procedure TSynLogFamily.SetLevel(const aLevel: TSynLogInfos); {$ifdef WITH_VECTOREXCEPT} var K32: HMODULE; {$endif} begin fLevel := aLevel; fHandleExceptions := (sllExceptionOS in aLevel) or (sllException in aLevel); if not SynLogExceptionEnabled and fHandleExceptions then begin SynLogExceptionEnabled := true; {$ifdef WITH_VECTOREXCEPT} K32 := GetModuleHandle(kernel32); AddVectoredExceptionHandler := GetProcAddress(K32,'AddVectoredExceptionHandler'); // RemoveVectoredContinueHandler() is available under 64 bit editions only if Assigned(AddVectoredExceptionHandler) then // available since Windows XP AddVectoredExceptionHandler(0,@SynLogVectoredHandler); {$else WITH_VECTOREXCEPT} oldUnWindProc := RTLUnwindProc; RTLUnwindProc := @SynRtlUnwind; {$endif WITH_VECTOREXCEPT} end; end; constructor TSynLogFamily.Create(aSynLog: TSynLogClass); begin fSynLogClass := aSynLog; if SynLogFamily=nil then begin SynLogFamily := TObjectList.Create; GarbageCollector.Add(SynLogFamily); end; fIdent := SynLogFamily.Add(self); fDestinationPath := ExtractFilePath(paramstr(0)); fBufferSize := 4096; fStackTraceLevel := 20; fExceptionIgnore := TList.Create; end; function TSynLogFamily.CreateSynLog: TSynLog; begin result := fSynLogClass.Create(self); if fPerThreadLog then begin if SynLogFile=nil then begin ................................................................................ AutoFlushThread: THandle = 0; AutoFlushSecondElapsed: cardinal; procedure AutoFlushProc(P: pointer); stdcall; // TThread not needed here var i: integer; begin repeat Sleep(1000); // thread will awake every second to check of pending data if SynLogFile=nil then continue; inc(AutoFlushSecondElapsed); for i := 0 to SynLogFile.Count-1 do with TSynLog(SynLogFile.List^[i]) do if (fFamily.fAutoFlush<>0) and (fWriter<>nil) and (AutoFlushSecondElapsed mod fFamily.fAutoFlush=0) then if fWriter.B-fWriter.fTempBuf>1 then begin if not IsMultiThread and not fWriterStream.InheritsFrom(TFileStream) then IsMultiThread := true; // only TFileStream is thread-safe Flush(false); // write pending data end; until false; end; procedure TSynLogFamily.SetAutoFlush(TimeOut: cardinal); var ID: cardinal; begin SynLog.CreateLocker; fAutoFlush := TimeOut; if (AutoFlushThread=0) and (TimeOut<>0) then begin AutoFlushSecondElapsed := 0; AutoFlushThread := CreateThread(nil,0,@AutoFlushProc,nil,0,ID); end; end; destructor TSynLogFamily.Destroy; begin FreeAndNil(fGlobalLog); if AutoFlushThread<>0 then begin CloseHandle(AutoFlushThread); // release background thread once for all AutoFlushThread := 0; end; FreeAndNil(fExceptionIgnore); inherited; end; function TSynLogFamily.SynLog: TSynLog; var ndx: integer; begin if fPerThreadLog then begin ................................................................................ ndx := SynLogFileIndex[fIdent]-1; if ndx>=0 then result := SynLogFile.List^[ndx] else result := CreateSynLog; end else if fGlobalLog<>nil then result := fGlobalLog else result := CreateSynLog; if fHandleExceptions then CurrentHandleExceptionSynLog := result; end; { TSynLog } ................................................................................ inc(fRecursionCount); result := fRecursionCount; end; function TSynLog._Release: Integer; begin dec(fRecursionCount); if sllLeave in fFamily.Level then begin asm mov eax,self mov ecx,[ebp+16] mov TSynLog.fRecursionCaller[eax],ecx end; fRecursion^[fRecursionCount].Caller := fRecursionCaller; DoEnterLeave(sllLeave); end; result := fRecursionCount; end; constructor TSynLog.Create(aFamily: TSynLogFamily); begin fFamily := aFamily; end; ................................................................................ begin inc(fRecursionMax,256+fRecursionMax shr 3); ReallocMem(fRecursion,fRecursionMax*sizeof(fRecursion^[0])); end; class function TSynLog.Enter(aClassType: TClass; aMethodName: PUTF8Char): ISynLog; var aSynLog: TSynLog; aStackFrame: PtrUInt; begin asm mov eax,[ebp+4] // retrieve caller EIP from push ebp; mov ebp,esp sub eax,5 // ignore call TSynLog.Enter op codes mov aStackFrame,eax end; aSynLog := Family.SynLog; with aSynLog do begin // recursively store parameters if fRecursionCount=fRecursionMax then RecursionGrow; with fRecursion^[fRecursionCount] do begin Instance := nil; ClassType := aClassType; Method := aMethodName; Caller := aStackFrame; end; end; // copy to ISynLog interface -> will call TSynLog._AddRef result := aSynLog; end; class function TSynLog.Enter(aInstance: TObject; aMethodName: PUTF8Char): ISynLog; var aSynLog: TSynLog; aFamily: TSynLogFamily; aStackFrame: PtrUInt; begin asm mov eax,[ebp+4] // retrieve caller EIP from push ebp; mov ebp,esp sub eax,5 // ignore call TSynLog.Enter op codes mov aStackFrame,eax end; // inlined aSynLog := Family.SynLog aFamily := PPointer(PtrInt(Self)+vmtAutoTable)^; if aFamily=nil then aSynLog := FamilyCreate.SynLog else aSynLog := aFamily.SynLog; // recursively store parameters with aSynLog do begin ................................................................................ RecursionGrow; with fRecursion^[fRecursionCount] do begin Instance := aInstance; if aInstance=nil then ClassType := pointer(aInstance) else ClassType := PPointer(aInstance)^; Method := aMethodName; Caller := aStackFrame; end; end; // copy to ISynLog interface -> will call TSynLog._AddRef result := aSynLog; end; class function TSynLog.FamilyCreate: TSynLogFamily; ................................................................................ procedure TSynLog.Log(Level: TSynLogInfo; aName: PWinAnsiChar; aTypeInfo: pointer; var aValue); begin if Level in fFamily.fLevel then LogInternal(Level,aName,aTypeInfo,aValue); end; procedure TSynLog.Log(Level: TSynLogInfo); var aCaller: PtrUInt; begin if Level in fFamily.fLevel then begin LogHeaderLock(Level); asm mov eax,[ebp+4] // retrieve caller EIP from push ebp; mov ebp,esp sub eax,5 // ignore call TSynLog.Enter op codes mov aCaller,eax end; TSynMapFile.Log(fWriter,aCaller); fWriter.AddCR; UnLock; end; end; procedure TSynLog.LogFileHeader; var Freq: Int64; begin QueryPerformanceFrequency(Freq); ExeVersionRetrieve; with ExeVersion, SystemInfo, OSVersionInfo do ................................................................................ procedure TSynLog.CreateLogWriter; begin if fWriterStream=nil then begin ExeVersionRetrieve; fFileName := string(ExeVersion.ProgramName)+' '+GetCaptionFromClass(ClassType)+' '+ TFileName(NowToString(false)); if fFamily.PerThreadLog then fFileName := fFileName+' '+IntToString(GetCurrentThreadId); fFileName := fFamily.fDestinationPath+fFileName+'.log'; fWriterStream := TFileStream.Create(fFileName,fmCreate) end; if fWriter=nil then fWriter := TTextWriter.Create(fWriterStream,fFamily.BufferSize); end; ................................................................................ if Instance<>nil then begin fWriter.Add('('); fWriter.AddPointer(PtrUInt(Instance)); fWriter.Add(')'); end; fWriter.Add('.'); end; if Method<>nil then fWriter.AddNoJSONEscape(Method) else TSynMapFile.Log(fWriter,Caller); end; fWriter.AddCR; end; procedure TSynLog.DoEnterLeave(aLevel: TSynLogInfo); begin LogHeaderLock(aLevel); |
Changes to SynSelfTests.pas.
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 ... 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 ... 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 |
var TestLevel: TOrdType = high(TOrdType); procedure TTestCompression.TestLog; var ILog: ISynLog; S: TSQLFieldTypes; begin ILog := TSQLLog.Enter(self,'TestLog'); // do some stuff ILog.Log(sllInfo,'TestLevel',TypeInfo(TOrdType),TestLevel); ILog.Log(sllInfo,'set',TypeInfo(TSQLFieldTypes),S); ILog.Log(sllDebug,ILog.Instance); if TestLevel=low(TOrdType) then TTestCompression(nil).ClassName; // will raise an access violation dec(TestLevel); TestLog; end; { TTestCryptographicRoutines } procedure TTestCryptographicRoutines.Adler32; begin Check(Adler32SelfTest); ................................................................................ StandardFontsReplace := embed; AddPage; Canvas.SetFont('arial',10,[]); Check(Canvas.Page.Font.Name=Name[embed]); y := 800; for i := 1 to 30 do begin Canvas.SetFont('Arial',9+i,[]); Canvas.TextOut(100,y,WinAnsiString('Texte accentu'#233' n�'+IntToStr(i))); dec(y,9+i); end; SaveToStream(MS,Date); // MS.SaveToFile(ChangeFileExt(paramstr(0),'.pdf')); Check(Hash32(MS.Memory,MS.Position)=Hash[embed]); if not embed then begin NewDoc; ................................................................................ finally Free; MS.Free; end; end; {$endif} begin if false then begin AllocConsole; with TSQLLog.Family do begin Level := LOG_VERBOSE; //Level := [sllException]; PerThreadLog := true; HighResolutionTimeStamp := true; AutoFlushTimeOut := 5; end; with TTestCompression.Create(nil) do try try TestLog; except on Exception do; // just ignore now end; finally Free; end; TSQLLog.Add.Log(sllDebug,'GarbageCollector',GarbageCollector); TSQLLog.Add.Log(sllDebug,GarbageCollector); TSynLog.Family.Level := [sllDebug]; TSynLog.Add.Log(sllDebug,GarbageCollector); try raise ECrtSocket.Create('Test exception',-1024); except on Exception do; // just ignore now end; halt; end; end. |
| > > > > > > > > > | < > > > | > > > > > > > > > > | > > > > > > > > > > | | | | | | | | < | < < > | | > | < < < < < < < > > > | < > > > > > > > > > > > > > > > > > > > > > | | > > > > |
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 ... 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 ... 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 |
var TestLevel: TOrdType = high(TOrdType); procedure TTestCompression.TestLog; var ILog: ISynLog; S: TSQLFieldTypes; begin ILog := TSQLLog.Enter(self); // do some stuff ILog.Log(sllCustom1); ILog.Log(sllInfo,'TestLevel',TypeInfo(TOrdType),TestLevel); ILog.Log(sllInfo,'set',TypeInfo(TSQLFieldTypes),S); ILog.Log(sllDebug,ILog.Instance); if TestLevel=low(TOrdType) then TTestCompression(nil).ClassName; // will raise an access violation dec(TestLevel); TestLog; end; procedure TestLogProc; var ILog: ISynLog; begin ILog := TSQLLog.Enter; ILog.Log(sllDebug,'GarbageCollector',GarbageCollector); ILog.Log(sllDebug,GarbageCollector); end; { TTestCryptographicRoutines } procedure TTestCryptographicRoutines.Adler32; begin Check(Adler32SelfTest); ................................................................................ StandardFontsReplace := embed; AddPage; Canvas.SetFont('arial',10,[]); Check(Canvas.Page.Font.Name=Name[embed]); y := 800; for i := 1 to 30 do begin Canvas.SetFont('Arial',9+i,[]); Canvas.TextOut(100,y,WinAnsiString('Texte accentu'#233' n�'+IntToString(i))); dec(y,9+i); end; SaveToStream(MS,Date); // MS.SaveToFile(ChangeFileExt(paramstr(0),'.pdf')); Check(Hash32(MS.Memory,MS.Position)=Hash[embed]); if not embed then begin NewDoc; ................................................................................ finally Free; MS.Free; end; end; {$endif} procedure TestsLog; procedure Proc2(n1, n2: Integer); forward; procedure Proc1(n1, n2: Integer); begin if n1 = 0 then try asm xor eax,eax; mov [eax],eax; end; // AV except on e: exception do TSQLLog.Add.Log(sllInfo,'^^^^^^^^ recursicve, Proc1 function',e); end else Proc2(n1 - 1, n2); end; procedure Proc2(n1, n2: Integer); begin if n2 = 0 then try asm xor eax,eax; mov [eax],eax; end; // AV except on e: exception do TSQLLog.Add.Log(sllInfo,'^^^^^^^^ recursicve, Proc2 function',e); end else Proc1(n1, n2 - 1); end; begin AllocConsole; with TSQLLog.Family do begin Level := LOG_VERBOSE; //Level := [sllException]; PerThreadLog := true; HighResolutionTimeStamp := true; //AutoFlushTimeOut := 5; end; try writeln(IOResult div IOResult); // will raise EDivByZero except on e: exception do TSQLLog.Add.Log(sllInfo,'^^^^^^^^ the first sample, divide by 0',e); end; Proc1(5, 7); Proc2(7, 5); with TTestCompression.Create(nil) do try try TestLog; except on Exception do; // just ignore now end; finally Free; end; TestLogProc; TSynLog.Family.Level := [sllDebug]; TSynLog.Add.Log(sllDebug,GarbageCollector); try raise ECrtSocket.Create('Test exception',-1024); // logged to TSQLLog except on e: Exception do TSQLLog.Add.Log(sllInfo,'^^^^^^^^ custom exception type',e); end; TSQLLog.Family.ExceptionIgnore.Add(ECrtSocket); try raise ECrtSocket.Create('Test exception',-1024); except on e: Exception do TSQLLog.Add.Log(sllInfo,'^^^^^^^^ nothing should be logged just above',e); end; halt; end; begin if false then TestsLog; end. |