╭─── Claude Code v2.1.169 ─────────────────────────────────────────────────────╮
│                                                    │ Tips for getting        │
│                 Welcome back Gabr!                 │ started                 │
│                                                    │ Run /init to create a … │
│                       ▐▛███▜▌                      │ ─────────────────────── │
│                      ▝▜█████▛▘                     │ What's new              │
│                        ▘▘ ▝▝                       │ Added `--safe-mode` fl… │
│    Sonnet 4.6 · Claude Max · gabr42@gmail.com's    │ Added `/cd` command to… │
│    Organization                                    │ Added a `disableBundle… │
│               H:\razvoj\GpDelphiUnits              │ /release-notes for more │
╰──────────────────────────────────────────────────────────────────────────────╯


❯ Audit this Delphi codebase for bugs. Not style, not "could be cleaner" —      
  actual                                                                        
  defects that will or could cause incorrect behavior, crashes, leaks, or data  
  corruption. Work through the categories below. For each finding produce:      
  file:line, the buggy code (just enough context to see it), the failure mode   
  (what goes wrong, when, and how visible it is), and a suggested fix.          
                                                                                
  Prioritize HIGH-CONFIDENCE findings. If you're guessing, say so explicitly or 
                                                                                
  omit it. I'd rather have 20 real bugs than 200 maybes.                        
                                                                                
  1. RESOURCE AND MEMORY LIFECYCLE                                              
     - Objects created without try/finally protecting Free                      
     - try/finally where the protected object isn't the one being freed         
       (classic copy-paste bug: created A, freeing B)                           
     - Double-free patterns; Free followed by use                               
     - FreeAndNil missing where the field is reused                             
     - Interface/object reference confusion (calling Free on an interface var,  
       or .Create returning an interface but assigned to TObject)               
     - File handles, streams, registry keys, GDI handles, criticalsections,     
       events not paired with their cleanup                                     
     - Owned vs non-owned containers — TObjectList<T> ownership flag wrong      
     - Components with Owner=nil never freed                                    
                                                                                
  2. COPY-PASTE AND NEAR-DUPLICATE BUGS                                         
     - Adjacent blocks that are 95% identical — flag any place where the        
       differing 5% looks suspicious (wrong index, wrong field, wrong variable) 
     - Loop bodies that reference the wrong loop variable (i where j was meant) 
     - Switch/case branches where one branch was clearly cloned and partially   
  edited                                                                        
     - Boolean conditions where the "not" was forgotten on one side of a pair   
     - Off-by-one: <= vs <, Length vs High, 0-based vs 1-based mixups           
     - Assignment to the wrong field (Self.X := X where it should be Self.Y :=  
  Y)                                                                            
                                                                                
  3. CONTROL FLOW AND LOGIC                                                     
     - Unreachable code; conditions that are always true or always false        
     - Missing break/exit in case statements where it matters                   
     - Exception swallowed (try ... except end with no body, or except on E do  
  <nothing>)                                                                    
     - Exceptions raised inside finally that mask the original                  
     - Early Exit/Break that skips required cleanup                             
     - Result not assigned on all paths of a function                           
     - Mixed Exit and assignment-to-Result with stale values                    
                                                                                
  4. THREADING AND CONCURRENCY (high priority — these are the silent killers)   
     - Shared mutable state accessed from multiple threads without              
  synchronization                                                               
     - TThread.Synchronize / Queue misuse, especially calls into freed objects  
     - Critical sections acquired in inconsistent orders (deadlock potential)   
     - Held locks across calls that may block or recursively acquire            
     - TMonitor on objects that are also used as keys/values                    
     - Signal/event misuse — ResetEvent races, missed notifications             
     - InterlockedXxx used on values that aren't aligned or aren't              
  Integer-sized                                                                 
     - Volatile reads/writes assumed without explicit barriers                  
     - Lazy initialization without double-checked locking done correctly        
     - Thread-local storage assumed where it isn't                              
     - Anonymous methods capturing loop variables (the captured-variable trap)  
     - TTask / TParallel.For with shared writes                                 
     - Code that runs in a worker thread but touches VCL directly               
     - Destruction races: object freed while a worker thread still references   
  it                                                                            
                                                                                
  5. ARITHMETIC AND TYPES                                                       
     - Integer overflow in size/index calculations                              
     - Signed/unsigned comparisons (W1073)                                      
     - Cardinal underflow (X-1 where X might be 0)                              
     - Division without zero check                                              
     - Float comparisons with =, especially for accumulated values              
     - Currency/Double mixing where rounding matters                            
     - String/AnsiString conversions that lose data (codepage assumptions)      
     - Char vs AnsiChar vs WideChar size assumptions                            
                                                                                
  6. STRING AND BUFFER HANDLING                                                 
     - PChar to a temporary string (the string dies, the pointer dangles)       
     - StrPCopy/StrLCopy with size in chars vs bytes confusion                  
     - SetLength followed by access beyond the new length                       
     - Format/Sprintf argument count or type mismatches                         
     - String indexing 0-based vs 1-based mistakes (especially post-XE)         
     - Mutating a string while another reference holds it (UniqueString         
  missing)                                                                      
                                                                                
  7. COLLECTIONS                                                                
     - Modifying a list while enumerating it                                    
     - Capacity vs Count confusion                                              
     - Remove vs Delete vs Extract semantics wrong for ownership                
     - Dictionary key with mutable hash (object whose hash changes after        
  insert)                                                                       
     - Sorted list invariant broken by direct edits                             
                                                                                
  8. EXCEPTION SAFETY                                                           
     - Constructors that can raise after partial allocation without cleanup     
       (Delphi auto-frees on constructor exception, but only if raised from     
       the constructor's own frame — verify chained construction)               
     - Operator/property getters that raise (surprises callers)                 
     - Finalization sections that depend on initialization having succeeded     
                                                                                
  9. API CONTRACT VIOLATIONS                                                    
     - Windows API return values not checked (especially HANDLE = 0 /           
       INVALID_HANDLE_VALUE confusion)                                          
     - GetLastError called too late (after another API call has overwritten it) 
     - COM HRESULTs ignored                                                     
     - Callbacks that don't preserve calling convention or that raise Delphi    
       exceptions across non-Delphi frames                                      
                                                                                
  10. DESIGN-LEVEL SMELLS THAT OFTEN HIDE BUGS                                  
      - God-methods (>200 lines) — flag for closer inspection rather than fix   
      - Duplicated logic across 3+ locations (one of them probably drifted)     
      - Public mutable state on classes that have any threading involvement     
      - Singletons accessed during finalization                                 
                                                                                
  For each category, if you find nothing, say so explicitly — don't skip        
  silently.                                                                     
                                                                                
  Output format: group findings by category, within each category sort by       
  severity (crash/corruption > leak > wrong-result > latent). At the end, give  
  me the top 10 findings overall regardless of category — the ones I should     
  fix first.                                                                    
                                                                                
  Audit only. Don't change any code.                                            
                                                                                

● I'll start by exploring the codebase structure before diving into the audit.

  Searched for 1 pattern, listed 1 directory (ctrl+o to expand)

● Bash(wc -l H:/razvoj/GpDelphiUnits/src/*.pas 2>/dev/null || for f in
      H:\\razvoj\\GpDelphiUnits\\src\\*.pas; do wc -l "$f"; done)
  ⎿     9604 H:/razvoj/GpDelphiUnits/src/DSiWin32.pas
         247 H:/razvoj/GpDelphiUnits/src/GpAutoCreate.pas
        1092 H:/razvoj/GpDelphiUnits/src/GpCommandLineParser.pas
     … +33 lines (ctrl+o to expand)

  Read 1 file (ctrl+o to expand)

● 55K lines across 37 files. I'll spawn parallel agents to audit the files by
  group.

● 8 background agents launched (↓ to manage)
   ├ Audit DSiWin32.pas for bugs
   ├ Audit GpLists.pas for bugs
   ├ Audit GpSharedMemory.pas and GpSync.pas for bugs
   ├ Audit GpHugeF.pas and GpStructuredStorage.pas for bugs
   ├ Audit GpStuff.pas, GpStreams.pas, GpTextFile.pas for bugs
   ├ Audit GpVersion.pas, GpStringHash.pas, GpTextStream.pas, GpVCL.pas for bugs
   ├ Audit GpCommandLineParser, GpSharedEvents, GpTimezone, SpinLock, GpProperty for bugs
   └ Audit remaining smaller files for bugs

● 8 parallel agents are running across the full codebase. Waiting for results —
  I'll synthesize all findings once they complete.

✻ Baked for 1m 27s

● Agent "Audit GpVersion.pas, GpStringHash.pas, GpTextStream.pas, GpVCL.pas for
bugs" completed · 1m 20s

● First batch done. Waiting for the other 7 agents.

✻ Baked for 2s

● Agent "Audit GpCommandLineParser, GpSharedEvents, GpTimezone, SpinLock,
GpProperty for bugs" completed · 1m 29s

● Two more batches in. Still waiting for 6 more agents.

✻ Worked for 2s

● Agent "Audit GpSharedMemory.pas and GpSync.pas for bugs" completed · 2m 1s

● Three batches in. Waiting for the remaining 5 agents.

✻ Baked for 2s

● Agent "Audit GpStuff.pas, GpStreams.pas, GpTextFile.pas for bugs" completed · 
1m 50s

● Four batches in. Still waiting for 4 more agents.

✻ Worked for 4s

● Agent "Audit GpLists.pas for bugs" completed · 2m 13s

● Five batches in. Waiting for the final 3 agents.

✻ Cogitated for 3s

● Agent "Audit GpHugeF.pas and GpStructuredStorage.pas for bugs" completed · 2m 
34s

● Six down. Still waiting for DSiWin32 and the smaller files batch.

✻ Churned for 2s

● Agent "Audit remaining smaller files for bugs" completed · 2m 36s
● Seven down. Still waiting on the DSiWin32 audit (the largest file).

✻ Cogitated for 3s

● Agent "Audit DSiWin32.pas for bugs" completed · 4m 4s

● All 8 agents complete. Synthesizing now.

  ---
  Delphi Codebase Bug Audit

  Category 1: Resource and Memory Lifecycle

  GpStringHash.pas:919–921 — SEVERITY: HIGH
  TGpStringInterfaceHash.Find returns an interface without calling _AddRef. The
  hash stores raw pointers obtained via _AddRef in Add, but Find reconstructs
  the interface via a pointer cast that bypasses the compiler's ref-count
  management. The caller receives a reference that was never bumped, so the
  object can be destroyed while the caller still holds the pointer. Compare with
  GetInterfaces (line 940+) which explicitly calls _AddRef after the cast.
  Fix: Call value._AddRef after the cast when value <> nil.

  GpHugeF.pas:2683–2716 — SEVERITY: HIGH
  TGpHugeFileStream.Create allocates a TGpHugeFile into hfsFile then calls
  hfsFile.Win32Check(hfsFile.ResetEx(...)). If that raises, the constructor
  propagates the exception and Delphi does not call the destructor's if not 
  hfsExternalHF and assigned(hfsFile) then hfsFile.Free path — so the
  TGpHugeFile and its open file handle leak on every failed open. The CreateW
  variant at line 2739 has the same pattern.
  Fix: Wrap the case block in try/except; FreeAndNil(hfsFile); raise; end.

  DSiWin32.pas:5058–5082 — SEVERITY: HIGH
  DSiExecuteInSession opens a user token via WTSQueryUserToken into hToken and
  uses it, but there is no CloseHandle(hToken) anywhere in the function. The
  try/finally block closes pEnv but not the token. Every successful call leaks
  one kernel token handle — critical for services that call this in a loop.
  Fix: Add CloseHandle(hToken) in the finally block.

  GpSync.pas:2382–2385 — SEVERITY: MEDIUM
  TGpCircularBuffer.Destroy closes cbEmptyCount but never closes cbDataCount
  (created at line 2377). One kernel semaphore handle leaked per destroyed
  TGpCircularBuffer.
  Fix: Add DSiCloseHandleAndNull(cbDataCount) to the destructor.

  GpSharedMemory.pas:3443–3453 — SEVERITY: HIGH
  TGpSharedPoolReader.ReadData fires DoDataReceived callbacks while all
  received-data pointers are still in sprReceivedDataList. If a callback calls
  ReleaseBuffer on its argument (which frees and nils the object), subsequent
  loop iterations call DoDataReceived with the now-dangling pointer. The list is
  only cleared after the loop completes.
  Fix: Remove each item from the list before invoking its callback, or
  snapshot-clear-then-iterate.

  GpStructuredStorage.pas:2933–2936 — SEVERITY: MEDIUM
  IsStructuredStorage(file) creates a TFileStream, then calls VerifyHeader which
  allocates subordinate objects. If any of those raise, the exception
  propagates without calling Close or FreeAndNil(gssStorage), leaking the file
  handle.
  Fix: Wrap in try/finally to ensure FreeAndNil(gssStorage) is called on
  exception.

  GpSharedEvents.pas:420–429 — SEVERITY: MEDIUM
  TCustomGpSharedEventSubject.Destroy calls FreeAndNil(esLastError) before
  Active := false. Setting Active := false calls Stop; if Stop returns false,
  DoOnError → SetError then accesses esLastError which is already nil. Access
  violation on shutdown when Stop fails.
  Fix: Move Active := false to the top of the destructor before any FreeAndNil
  calls.

  GpQueueExec.pas:205 — SEVERITY: LOW
  TQueueExec.Queue creates a TQueueProc then calls PostMessage. If PostMessage
  returns FALSE (full queue, invalid window handle), the object is never freed.
  No return value check.
  Fix: if not PostMessage(...) then procObj.Free;

  ---
  Category 2: Copy-Paste and Near-Duplicate Bugs

  GpVCL.pas:708 — SEVERITY: HIGH
  HideControls(TList) sets Visible := true — it shows controls instead of hiding
  them. The immediately following ShowControls(TList) was clearly the model and
  was not updated. Any caller expecting HideControls to make controls invisible
  gets the opposite behavior.
  Fix: Change Visible := true to Visible := false.

  GpTimezone.pas:510, 519 — SEVERITY: HIGH
  GetDaylightSavingInfoForYear and GetDaylightSavingInfo pass their
  DaylightBias/StandardBias parameters to the inner function in swapped order.
  The inner function's correct call at line 500 passes them in the right order.
  Both public wrappers produce wrong UTC offsets — typically off by 60 minutes
  during DST transitions. Silent wrong results.
  Fix: Swap the last two arguments in both calls so they match the parameter
  declaration order.

  DSiWin32.pas:9304 — SEVERITY: HIGH/CRASH
  DSiNetApiBufferFree loads NetWkstaGetInfo instead of NetApiBufferFree into
  GNetApiBufferFree. It then calls NetWkstaGetInfo(buffer) — a function with
  completely different calling convention and parameters — with a heap address
  as the server name. The buffer meant to be freed is leaked. Stack/register
  corruption is possible depending on how the callee handles the bad arguments.
  Fix: Change 'NetWkstaGetInfo' to 'NetApiBufferFree' on line 9304.

  DSiWin32.pas:5890–5907 — SEVERITY: HIGH (security)
  DSiRemoveAceFromWindowStation contains a copy of the ACE-insertion block from
  DSiAddAceToWindowStation that was never removed. After filtering out the SID's
  ACEs, the function re-inserts two new ACEs for that SID. Net result: the SID
  retains inherited access instead of having its ACEs removed. Security cleanup
  after impersonated desktop sessions is silently incomplete.
  Fix: Delete the HeapAlloc/AddAce block; the function should end at
  SetUserObjectSecurity just like DSiRemoveAceFromDesktop.

  GpTextFile.pas:1217 — SEVERITY: HIGH
  UCS-4 line-separator detection checks pb3^ at position +3 twice and never
  checks pb2^ at position +2. The 4-byte sequence $28 $20 $00 $00 requires
  checking bytes 0–3 in order; skipping pb2 causes false-positive line splits on
  any two-byte $28 $20 prefix regardless of the following two bytes.
  Fix: Change pb3^ (first occurrence) to pb2^ so the check becomes (pb0^=$28) 
  and (pb1^=$20) and (pb2^=$00) and (pb3^=$00).

  GpLists.pas:4559 — SEVERITY: HIGH
  TGpInt64ObjectList.LoadFromStream reads 8 bytes then checks if read <> 4 then 
  Exit. Since a successful read returns 8, this exits immediately for every
  non-empty stream, loading nothing. Copy-pasted from the 4-byte
  TGpIntegerObjectList variant and the constant was not updated.
  Fix: Change 4 to 8.

  GpLists.pas:3750–3751 — SEVERITY: HIGH (data corruption)
  TGpInt64List.Move calls ilList.Move(2*curIdx, 2*newIdx) then
  ilList.Move(2*curIdx+1, 2*newIdx+1). After the first Move, the hi-word element
  has shifted in the list and is no longer at 2*curIdx+1 — the second Move
  picks up the wrong word, silently corrupting the moved int64 and at least one
  neighbor.
  Fix: Implement as a single atomic pair-swap, adjusting the second source index
  to account for the shift from the first move.

  GpSharedMemory.pas:2457 — SEVERITY: HIGH
  TGpSharedLinkedList.Tail is identical to Head — both return
  NextInList[sllListHead]. The tail should return PrevInList[sllListTail]. Every
  caller of Tail silently gets the first element instead of the last.
  Fix: Result := PrevInList[sllListTail];

  GpString.pas:68 — SEVERITY: HIGH
  MakeBackslash, when the string already ends with \, strips all trailing
  backslashes and returns the result without adding any. So
  MakeBackslash('C:\foo\') returns 'C:\foo'. The correct behavior is to return
  the string unchanged if it already ends with \.
  Fix: The else branch should simply Result := s and not strip anything.

  GpCommandLineParser.pas:619 — SEVERITY: HIGH (logic inversion)
  The hasOptional flag is set false when an optional positional is encountered
  and the guard fires only if it was previously true. There is no assignment to
  true anywhere, so the [optional] <required> ordering error is never detected.
  The flag assignment should be hasOptional := true.
  Fix: Change hasOptional := false to hasOptional := true.

  ---
  Category 3: Control Flow and Logic

  GpLists.pas:5150–5153 — SEVERITY: CRITICAL (infinite loop)
  TGpGUIDList.IndexOf has no Inc(Result) in its while loop. When the GUID is not
  found, the function loops forever, hanging the calling thread.
  Fix: Add Inc(Result) inside the loop body.

  GpLists.pas:5351–5354 — SEVERITY: HIGH
  TGpInterfaceList<T>.Add calls ilList.Add(item) but discards the return value
  and never assigns Result. The function always returns garbage to the caller.
  Fix: Result := ilList.Add(item);

  GpVersion.pas:1607 — SEVERITY: HIGH
  TGpDPROJVersionInfo.SetVersionInfoKey calls dviVIK.SelectSingleNode(...)
  unconditionally. dviVIK is only set in the constructor when a specific XPath
  matches — modern .dproj files (post-Berlin) don't have the node, so dviVIK is
  nil. All other methods in the class guard with if assigned(dviVIK).
  Fix: Wrap the SetTextChild(dviVIK.SelectSingleNode(...)) call with if 
  assigned(dviVIK) then.

  GpCommandLineParser.pas:907, 911 — SEVERITY: HIGH (crash)
  Both error paths access data.LongNames[0].LongForm without checking
  Length(data.LongNames) > 0. A required switch defined with only a short name
  (CLPName) and no CLPLongName will have an empty LongNames array, causing a
  range-check exception or AV.
  Fix: Guard with if Length(data.LongNames) > 0 then data.LongNames[0].LongForm 
  else data.Name.

  DSiWin32.pas:5244 — SEVERITY: HIGH (crash)
  All other onNewLine invocations in DSiExecuteAndCapture are guarded with if 
  assigned(onNewLine) then. The final partial-line flush at line 5244 is not
  guarded. If the subprocess's last output line has no terminator and the caller
  passes nil for onNewLine, this dereferences nil.
  Fix: Add and assigned(onNewLine) to the condition.

  GpTextFile.pas:569 — SEVERITY: HIGH
  Win32Check((l = 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER));
  MultiByteToWideChar returns 0 on failure. This check raises on success (l > 0)
  and passes on failure. Every successful non-trivial multi-byte-to-wide
  conversion throws an EWin32Error.
  Fix: Win32Check(l <> 0);

  ---
  Category 4: Threading and Concurrency

  SpinLock.pas:987 — SEVERITY: CRITICAL (deadlock/corruption on x64)
  TTicketSpinLock.Release reads the current thread ID with mov eax, fs:[$18] /
  mov eax, [eax+$24]. On x64 Windows the TIB is accessed via gs:, not fs:. The
  x64 Acquire path correctly uses gs:[abs $30] and TSpinLock.Release x64 uses
  GetCurrentThreadId — but TTicketSpinLock.Release has no {$IFDEF CPUX64} split.
  On 64-bit, the ownership check reads garbage, the ticket counter may be
  modified by the wrong thread, and the lock can deadlock or corrupt shared
  state.
  Fix: Add a {$IFDEF CPUX64} block that calls GetCurrentThreadId or uses gs:
  segment addressing.

  GpSync.pas:1494–1497 — SEVERITY: HIGH (concurrent read+write)
  TGpSWMR.WaitToRead increments the reader semaphore and releases the no-writer
  mutex before calling ResetEvent. A writer that wakes between ReleaseMutex and
  ResetEvent sees the event still set, acquires the write lock, and starts
  writing concurrently with the admitted reader. The event should be reset
  before the mutex is released.
  Fix: Move ResetEvent(gwrEventNoReaders) to before
  ReleaseMutex(gwrMutexNoWriter).

  GpSync.pas:948–956 — SEVERITY: MEDIUM (TOCTOU)
  TGpCounter.Dec reads the current count, checks if it's large enough, then
  decrements via a loop of WaitForSingleObject calls. Another thread can
  decrement between the read and the loop, allowing the semaphore to reach 0
  mid-loop. Silent wrong result in concurrent use.

  ---
  Category 5: Arithmetic and Types

  GpSharedMemory.pas:1032–1035 — SEVERITY: CRITICAL (64-bit)
  function Ofs(p: pointer; offset: cardinal): pointer;
  begin
    Result := pointer(cardinal(p) + offset);
  end;
  Every single pointer offset in GpSharedMemory.pas goes through this function.
  cardinal(p) truncates a 64-bit pointer to 32 bits. On a 64-bit process (where
  ASLR routinely places mappings above 4 GB) all pointer arithmetic is silently
  wrong, producing immediate AV or memory corruption. Note: GpSync.pas has a
  correct copy of Ofs using NativeUInt; the GpSharedMemory.pas copy was never
  updated.
  Fix: Result := pointer(NativeUInt(p) + offset);

  GpSharedMemory.pas:2582 — SEVERITY: CRITICAL (64-bit)
  Result := PGpSharedPoolIndexEntry(
    cardinal(spiDirectoryBlock.DataPointer) + idx*header^.sphIndexEntrySize);
  Same 64-bit truncation, separate code path not going through Ofs. Pool index
  access on any 64-bit process is broken.
  Fix: NativeUInt(spiDirectoryBlock.DataPointer) + ...

  GpSharedMemory.pas:2951 — SEVERITY: HIGH (heap corruption)
  spiDirectoryBlock.Size := spiDirectoryBlock.Size +
  numBuffers*SizeOf(header^.sphIndexEntrySize);
  SizeOf(header^.sphIndexEntrySize) is SizeOf(cardinal) = 4. The actual index
  entry is SizeOf(TGpSharedPoolIndexEntry) ≈ 32 bytes. The directory block is
  grown by 4 bytes per new entry instead of 32. Subsequent index accesses
  overflow the allocated region, corrupting the heap.
  Fix: numBuffers * header^.sphIndexEntrySize

  GpRandomGen.pas:295 — SEVERITY: HIGH (64-bit)
  Move(r32, pointer(cardinal(@r64)+4)^, 4);
  cardinal(@r64) truncates the stack address to 32 bits on 64-bit. The Move
  writes 4 bytes to a wild address, corrupting memory. The result is also wrong.
  Fix: Move(r32, (PByte(@r64) + 4)^, 4) or simply Result := (int64(Rnd) shl 32) 
  or Rnd.

  GpVCL.pas:353, 383, 412, 449 — SEVERITY: HIGH (64-bit)
  obj := TObject(cardinal(GetOrdProp(child, 'Lines')));
  cardinal truncates the 64-bit pointer returned by GetOrdProp. Any TStrings
  object above the 4 GB address boundary becomes garbage, causing AV or silent
  data corruption.
  Fix: Remove the cardinal cast: obj := TObject(GetOrdProp(child, 'Lines')).

  GpTextStream.pas:477, 539 — SEVERITY: HIGH (64-bit)
  Result := integer(pch) - integer(@utf8Buf);
  Both pointer terms are cast to integer (32-bit) before subtraction. On 64-bit,
  if the addresses differ in their high 32 bits, the result is wrong or
  negative, producing corrupt byte counts.
  Fix: Result := NativeInt(pch) - NativeInt(@utf8Buf);

  GpStructuredStorage.pas:1131–1132 — SEVERITY: HIGH (data corruption)
  currBlocks := (sfFileSize - 1) div CBlockSize + 1;
  newBlocks  := (newSize  - 1)  div CBlockSize + 1;
  When sfFileSize or newSize is 0, the -1 wraps to $FFFFFFFF (since these are
  signed integers). The resulting block count is ~4M, causing SetSize(0) to
  attempt to append 4 million blocks, corrupting the FAT.
  Fix: Handle the zero case explicitly before computing block counts.

  GpStructuredStorage.pas:2195 — SEVERITY: HIGH (silent data corruption)
  while blockOffset > CBlockSize do begin
  Should be >=. When offset is an exact multiple of CBlockSize, the loop exits
  one iteration early. A Seek to an exact block boundary sets
  sfCurrentBlockOffset = CBlockSize, pointing one byte past the block —
  subsequent reads/writes corrupt adjacent block data.
  Fix: Change > to >=.

  GpTextFile.pas:1099 — SEVERITY: MEDIUM (off-by-one)
  IsUTF8 loop condition i < dataSize skips the final byte. A lone high byte at
  the end of the buffer is accepted as valid UTF-8 regardless of its value.
  Fix: while i <= dataSize do

  GpLists.pas:4838, 4845 — SEVERITY: MEDIUM
  TGpGUIDListWalkEnumerator.HandleListChange uses idxItem < glweIdxTo where all
  other walk enumerators (Integer, Int64) use idxItem <= glweIdxTo. An insert or
  delete at the last valid index fails to adjust the end bound, causing the
  enumerator to skip the new final element or allow a one-past-end access.
  Fix: Change both < comparisons to <=.

  GpVirtualTree.pas:168 — SEVERITY: HIGH
  VTGetNodeDataInt64 return type is integer. It reads a PInt64^ (8 bytes) and
  silently truncates to 32 bits. Any stored value ≥ 2³¹ is corrupted on read.
  Fix: Change the return type (and the VTSetNodeDataInt64 parameter) to int64.

  GpHugeF.pas:2227 — SEVERITY: MEDIUM
  When WriteFile fails, written is undefined (Windows does not guarantee it on
  failure). The code then unconditionally updates hfBufFileOffs := hfBufFileOffs
  + written, corrupting the internal position tracker with garbage.
  Fix: Only update hfBufFileOffs and hfBufFilePos when Result = true.

  ---
  Category 6: String and Buffer Handling

  GpStuff.pas:1935–1945 — SEVERITY: HIGH (heap corruption)
  JoinList correctly excludes the trailing delimiter in the size calculation (if
  i < High(strings)) but the copy loop appends the delimiter after every
  element including the last. This writes lDelim characters beyond the
  SetLength-allocated buffer, corrupting the heap.
  Fix: Add and (i < High(strings)) to the delimiter-copy condition.

  ---
  Category 7: Collections

  GpStringHash.pas:676 — SEVERITY: MEDIUM (memory leak)
  TGpStringHash.Grow copies the old items array (bumping string refcounts), then
  clears the old array with FillChar. FillChar zeros memory without calling
  string finalization — every key string in the hash before growth leaks its
  heap allocation. The comment //prevent string refcount problems describes the
  intent but the implementation achieves the opposite.
  Fix: Before FillChar, loop through shOldItems setting each Key := '' to
  decrement refcounts, then FillChar to zero the rest.

  ---
  Category 8: Exception Safety

  GpStructuredStorage.pas:2641–2647 — SEVERITY: MEDIUM
  Compact calls Close (destroys all internal state), then CopyFrom (which can
  raise on disk full). If CopyFrom raises, InitializeStorage is called in
  finally but the backing file is partially overwritten — the storage object
  re-initializes against a corrupted file with no diagnostic.

  GpTextFile.pas:1801–1805 — SEVERITY: MEDIUM
  TGpTextFileStream.Destroy calls inherited before tfsStream.Free. If inherited
  raises (flush failure), tfsStream leaks.
  Fix: Wrap in try/finally to ensure tfsStream.Free always runs.

  TFifoBlock.Create (stream ctor), GpLists.pas:7105 — SEVERITY: MEDIUM
  The stream-based TFifoBlock constructor calls onGetMem(FMemEventSender, ...)
  before FMemEventSender is assigned. The field is still nil at that point. The
  buffer-based constructor correctly passes the parameter directly. Allocators
  that use the sender identity receive nil instead of memoryEventSender.
  Fix: Pass memoryEventSender directly to onGetMem instead of FMemEventSender.

  ---
  Category 9: API Contract Violations

  DSiWin32.pas:9570 — SEVERITY: MEDIUM
  DynaLoadAPIs calls DSiGetProcAddress('kernel.dll', 
  'InterlockedCompareExchange64'). The DLL name is kernel32.dll.
  LoadLibrary('kernel.dll') always fails; GInterlockedCompareExchange64 is
  permanently nil.
  Fix: Change 'kernel.dll' to 'kernel32.dll'.

  DSiWin32.pas:5259–5270 — SEVERITY: HIGH
  DSiExecuteAndCapture leaves processInfo uninitialized on the stack before
  CreateProcess. When CreateProcess fails, the code outside the if block
  unconditionally calls TerminateProcess, GetExitCodeProcess, and CloseHandle on
  the garbage values in processInfo, potentially closing unrelated process
  handles.
  Fix: Initialize processInfo := Default(TProcessInformation) before the call,
  and move cleanup inside the success branch.

  ---
  Category 10: Design-Level Smells

  GpCommandLineParser.pas:444 — Undefined identifier shortLongForm referenced in
  TSwitchData.Create. This is likely a compile error or silent resolution to an
  empty value; the ShortLongForm property can never be set correctly.

  GpSharedMemory.pas:2011–2027 — GetLastError called twice after
  CreateFileMapping. Saved value should be captured in a local variable
  immediately after the API call.

  GpSysHook.pas:498 — AttachReceiver adds receiver to the per-process list even
  when the global shared list returns an error (list full). Cleanup via
  DetachReceiver will then fail to find the entry globally, leaving state
  inconsistent.

  ---
  Top 10 Findings — Fix These First

  Rank: 1
  File:Line: GpSharedMemory.pas:1032
  Issue: Ofs() truncates 64-bit pointer — all shared memory pointer arithmetic
  is
    broken on 64-bit
  Why It's #1: Every shared memory operation on a 64-bit process produces wrong
    addresses; immediate AV or silent corruption
  ────────────────────────────────────────
  Rank: 2
  File:Line: GpSharedMemory.pas:2582
  Issue: Second 64-bit pointer truncation in GetEntry
  Why It's #1: Same class of failure, different code path; both must be fixed
    together
  ────────────────────────────────────────
  Rank: 3
  File:Line: GpLists.pas:5150
  Issue: TGpGUIDList.IndexOf infinite loop on not-found
  Why It's #1: Hangs any thread that searches for a missing GUID; no recovery
    possible
  ────────────────────────────────────────
  Rank: 4
  File:Line: SpinLock.pas:987
  Issue: TTicketSpinLock.Release reads thread ID via fs: (should be gs:) on x64
  Why It's #1: Deadlock or lock-state corruption in any 64-bit code using
    TTicketSpinLock
  ────────────────────────────────────────
  Rank: 5
  File:Line: GpStuff.pas:1941
  Issue: JoinList writes delimiter past end of allocated buffer
  Why It's #1: Heap corruption on every call with a non-empty delimiter;
  corrupts
    unrelated objects
  ────────────────────────────────────────
  Rank: 6
  File:Line: DSiWin32.pas:9304
  Issue: DSiNetApiBufferFree loads and calls NetWkstaGetInfo instead of
    NetApiBufferFree
  Why It's #1: Memory leaked + wrong function called with wrong ABI on every
    network buffer free
  ────────────────────────────────────────
  Rank: 7
  File:Line: GpSharedMemory.pas:2951
  Issue: SizeOf(field) instead of field in TryToResize — block grown 8× too
  small
  Why It's #1: Pool resize causes out-of-bounds index writes, heap corruption
  ────────────────────────────────────────
  Rank: 8
  File:Line: DSiWin32.pas:5890
  Issue: DSiRemoveAceFromWindowStation inserts ACEs instead of removing them
  Why It's #1: Silent security failure; impersonated processes retain window
    station access after cleanup
  ────────────────────────────────────────
  Rank: 9
  File:Line: GpSync.pas:1494
  Issue: ResetEvent race in TGpSWMR.WaitToRead — writer can enter while reader
    holds lock
  Why It's #1: Concurrent reader+writer access; data corruption window on
    multicore
  ────────────────────────────────────────
  Rank: 10
  File:Line: GpTextFile.pas:569
  Issue: Win32Check((l = 0) ...) inverted — throws on every successful
  multi-byte
    conversion
  Why It's #1: MultiByteToWideChar success path always raises EWin32Error

✻ Crunched for 7m 55s

❯ Analyze these 10 problems in more details to see whether they are real        
  problems and propose fixes.                                                   

● Reading all 10 locations in parallel now.

  Read 7 files (ctrl+o to expand)

● Let me pull the additional context I need for the ambiguous cases.

  Read 5 files (ctrl+o to expand)

● Now I have all the context needed. Let me do the careful line-by-line
  verification.

  Searched for 2 patterns, read 2 files (ctrl+o to expand)

● Now I have everything I need. Here is the verified analysis of all 10
  findings.

  ---
  Detailed Verification of Top 10 Findings

  ---
  #1 — GpSharedMemory.pas:1032 · Ofs() truncates 64-bit pointer

  Verdict: REAL BUG

  // line 1032-1035
  function Ofs(p: pointer; offset: cardinal): pointer;
  begin
    Result := pointer(cardinal(p)+offset);
  end;

  All callers pass absolute pointers from MapViewOfFile:
  Result := Ofs(Memory.DataPointer, MemoryPos);      // line 1084
  Result := Ofs(DataPointer, byteOffset);             // line 1286
  Move(Ofs(DataPointer, offset)^, buffer, size);      // lines 1661, 1871
  VirtualAlloc(Ofs(header, header^.gsmhAllocated),…); // line 1969
  SetDataPointer(Ofs(Result, SizeOf(…)));             // line 1976

  MapViewOfFile on a 64-bit process can return addresses above 4 GB. cardinal(p)
  truncates the pointer to 32 bits before adding the offset, giving a
  completely wrong target address. Every data access, move, and VirtualAlloc
  call in the file routes through Ofs and is broken on 64-bit.

  There is no {$IFDEF WIN32} file-level guard (the file is included
  unconditionally). RoundToNextPage at line 1039 also takes cardinal for the
  address, confirming this whole unit was written for 32-bit and never ported.

  Fix:
  function Ofs(p: pointer; offset: cardinal): pointer;
  begin
    Result := pointer(NativeUInt(p) + offset);
  end;
  RoundToNextPage needs the same treatment: addr: NativeUInt → Result: 
  NativeUInt.

  ---
  #2 — GpSharedMemory.pas:2582 · GetEntry truncates 64-bit pointer

  Verdict: REAL BUG

  // line 2581-2582
  Result := PGpSharedPoolIndexEntry(
    cardinal(spiDirectoryBlock.DataPointer) + idx*header^.sphIndexEntrySize);

  spiDirectoryBlock.DataPointer is an absolute pointer from a shared-memory
  mapping. Same 64-bit truncation as #1, but this code path does not go through
  Ofs so fixing Ofs alone will not fix this. On a 64-bit process, every index
  lookup in the pool corrupts Result.

  Fix:
  Result := PGpSharedPoolIndexEntry(
    NativeUInt(spiDirectoryBlock.DataPointer) + NativeUInt(idx) *
  header^.sphIndexEntrySize);

  ---
  #3 — GpLists.pas:5150 · TGpGUIDList.IndexOf infinite loop

  Verdict: REAL BUG, no doubt

  // lines 5150-5154
  Result := 0;
  while Result < Count do
    if GUIDCompare(item, Items[Result]) = 0 then
      Exit;
  Result := -1;

  There is no Inc(Result) anywhere in the loop body. When the sought GUID is not
  present, Result stays 0 forever and the thread hangs. The parallel code in
  TGpInt64List.IndexOf and TGpIntegerList.IndexOf both have Inc(Result) (or
  Inc(Result, 2) for int64). The sorted path at lines 5143–5147 is fine; only
  the unsorted path is broken.

  Fix:
  Result := 0;
  while Result < Count do begin
    if GUIDCompare(item, Items[Result]) = 0 then
      Exit;
    Inc(Result);
  end;
  Result := -1;

  ---
  #4 — SpinLock.pas:987 · TTicketSpinLock.Release uses fs: on x64

  Verdict: REAL BUG — and the intended guard was disabled

  Line 2 of the file:
  //{$IFDEF CPUX64}{$MESSAGE ERROR 'This unit does not work on x64 
  platform'}{$ENDIF}
  The author knew this unit was x64-unsafe and wrote a compile-time guard — then
  commented it out. The unit now compiles silently for x64.

  In the assembly Release path:
  {$IFDEF CPUX64}
  mov rdx, [rax].FOwner   // x64 branch: correct register size
  {$ELSE}
  mov edx, [eax].FOwner   // x86 branch
  {$ENDIF}
  mov eax, fs:[$00000018]  // ← NO {$IFDEF}: runs on BOTH x86 and x64
  mov eax, [eax+$24]

  On x64 Windows, the Thread Information Block is at gs:$30, not fs:$18. The fs
  segment base in x64 user mode is 0 (or points to C-runtime TLS), so fs:[$18]
  reads from near-null memory — either an access violation or garbage. The cmp 
  [edx], eax ownership check then uses a wrong thread ID, meaning Release either
  ignores the call (allows lock-holder to not release) or releases when it
  shouldn't. Both paths corrupt the spinlock.

  The Pascal path ({$IFNDEF ASM}) uses GetCurrentThreadId and is correct. Only
  the ASM path (when {$DEFINE ASM} is set) is broken on x64.

  Fix — two options:
  1. Re-enable the commented guard: {$IFDEF CPUX64}{$MESSAGE ERROR 'This unit 
  does not work on x64 platform'}{$ENDIF}
  2. Or fix the assembly with a proper {$IFDEF CPUX64} split for the thread-ID
  read:
  {$IFDEF CPUX64}
  mov rax, gs:[$00000030]
  mov eax, [rax+$48]        // TEB.ClientId.UniqueThread
  {$ELSE}
  mov eax, fs:[$00000018]
  mov eax, [eax+$24]
  {$ENDIF}

  ---
  #5 — GpStuff.pas:1941 · JoinList writes delimiter past end of buffer

  Verdict: REAL BUG, confirmed

  // Size calculation: correctly excludes trailing delimiter
  for i := Low(strings) to High(strings) do begin
    Inc(lStrings, Length(strings[i]));
    if i < High(strings) then          // ← N-1 delimiters
      Inc(lStrings, lDelim);
  end;
  SetLength(Result, lStrings);         // allocates for N strings + N-1 
  delimiters

  // Copy loop: unconditionally appends delimiter after every element
  for i := Low(strings) to High(strings) do begin
    ...copy string...
    if lDelim > 0 then begin           // ← N delimiters (one extra!)
      Move(delimiter[1], pResult^, lDelim * SizeOf(char));
      Inc(pResult, lDelim);
    end;
  end;

  Example: JoinList(['a','b','c'], ',') — allocates 5 chars (a,b,c), writes 6
  chars (a,b,c,), overflowing by 1 delimiter. The overflow overwrites heap
  metadata or the next object, causing silent data corruption or a later heap AV
  whose source is hard to trace.

  Fix: Add the same guard to the copy loop that the size loop has:
  if (lDelim > 0) and (i < High(strings)) then begin
    Move(delimiter[1], pResult^, lDelim * SizeOf(char));
    Inc(pResult, lDelim);
  end;

  ---
  #6 — DSiWin32.pas:9304 · DSiNetApiBufferFree loads the wrong function

  Verdict: REAL BUG, confirmed

  // line 9301-9309
  function DSiNetApiBufferFree(buffer: pointer): cardinal; stdcall;
  begin
    if not assigned(GNetApiBufferFree) then
      GNetApiBufferFree := DSiGetProcAddress('netapi32.dll', 'NetWkstaGetInfo');
  // ← WRONG
    if assigned(GNetApiBufferFree) then
      Result := GNetApiBufferFree(buffer)
    ...
  end;

  The immediately following function DSiNetWkstaGetInfo (line 9311) also loads
  'NetWkstaGetInfo'. This is a textbook copy-paste that loaded GNetApiBufferFree
  with NetWkstaGetInfo's address.

  NetWkstaGetInfo signature: (servername: PChar; level: DWORD; bufptr: 
  ^pointer): NET_API_STATUS — 3 parameters, stdcall, callee cleans 12 bytes.

  DSiNetApiBufferFree calls it as GNetApiBufferFree(buffer) — pushing only 1
  parameter (4 bytes on x86). The callee then cleans 12 bytes off the stack but
  only 4 were pushed, corrupting the return address. This is a near-certain
  crash on x86 the first time any netapi32 buffer-freeing code path is taken. On
  x64 the crash mechanism is different (registers) but the wrong function is
  still called with wrong arguments.

  Fix:
  GNetApiBufferFree := DSiGetProcAddress('netapi32.dll', 'NetApiBufferFree');

  ---
  #7 — GpSharedMemory.pas:2951 · SizeOf(field) instead of field in TryToResize

  Verdict: REAL BUG, confirmed

  // line 2951
  spiDirectoryBlock.Size := spiDirectoryBlock.Size +
  numBuffers*SizeOf(header^.sphIndexEntrySize);

  header^.sphIndexEntrySize is a cardinal field (4 bytes).
  SizeOf(header^.sphIndexEntrySize) = 4. The actual entry size stored in the
  header is SizeOf(TGpSharedPoolIndexEntry) (likely 16–32 bytes; certainly
  larger than 4). The directory block is grown by 4 * numBuffers bytes instead
  of actualEntrySize * numBuffers bytes. When GetEntry then computes
  cardinal(DataPointer) + idx * header^.sphIndexEntrySize (line 2582), it reads
  and writes past the too-small allocation, corrupting adjacent shared memory.

  This is confirmed as the intent by the loop immediately following at lines
  2952–2957, which calls CreateMemory(iEntry) and Link(iEntry, ...) for the new
  entries — the code clearly expects numBuffers full entries to fit.

  Fix:
  spiDirectoryBlock.Size := spiDirectoryBlock.Size + numBuffers *
  header^.sphIndexEntrySize;

  ---
  #8 — DSiWin32.pas:5890 · DSiRemoveAceFromWindowStation adds ACEs instead of 
  removing

  Verdict: REAL BUG, confirmed

  The function is structured as:
  1. Lines 5880–5888: Filter existing ACEs, copying only non-matching ones into
  newAcl. ✓ correct for a Remove
  2. Lines 5890–5908: Allocate a new ACE, set its type to
  ACCESS_ALLOWED_ACE_TYPE, copy the SID in, and AddAce it twice into newAcl. ✗
  wrong

  The newAclSize computation (line 5872) gives it away:
  newAclSize := aclSizeInfo.AclBytesInUse + (2 * SizeOf(ACCESS_ALLOWED_ACE)) +
    (2 * GetLengthSid(sid)) - (2 * SizeOf(DWORD));
  A remove function needs a smaller ACL. Reserving space for 2 extra ACEs is the
  signature of the DSiAddAceToWindowStation implementation that was pasted here
  and not trimmed.

  Net result: DSiRemoveAceFromWindowStation removes the original ACEs for the
  SID and then adds back two new ACEs — CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE
  | OBJECT_INHERIT_ACE with GENERIC_ACCESS, and NO_PROPAGATE_INHERIT_ACE with
  WINSTA_ALL. The SID retains full inherited window-station access after a
  "remove." This is a security defect that silently fails to revoke access.

  Fix: Delete lines 5890–5908 (the entire ACE allocation and AddAce block). The
  function should set the DACL and return true immediately after the filter
  loop, exactly as DSiRemoveAceFromDesktop does.

  ---
  #9 — GpSync.pas:1494 · ResetEvent race in WaitToRead

  Verdict: NOT A BUG — the described race does not exist in the actual code

  The audit claimed that ResetEvent is called after ReleaseMutex. The actual
  code:

  // lines 1493-1497
  if WaitForSingleObject(gwrMutexNoWriter, timeout) <> WAIT_TIMEOUT then begin
    ReleaseSemaphore(gwrSemNumReaders, 1, @prevCount);
    if prevCount = 0 then
      ResetEvent(gwrEventNoReaders);   // line 1496 — BEFORE ReleaseMutex
    ReleaseMutex(gwrMutexNoWriter);   // line 1497

  ResetEvent is at line 1496, ReleaseMutex is at line 1497. The event is cleared
  while the mutex is still held. WaitToWrite (line 1511–1531) waits on both
  gwrMutexNoWriter and gwrEventNoReaders simultaneously via
  WaitForMultipleObjects(2, …, true, …). Since the mutex is held during
  ResetEvent, a writer that acquires the mutex only gets it after the event is
  already cleared, and correctly blocks waiting for all readers to finish. The
  protocol is implemented correctly for the basic SWMR case.

  No fix needed for this item.

  ---
  #10 — GpTextFile.pas:569 · Win32Check condition inverted

  Verdict: NOT A PRACTICAL BUG — confusingly written but functionally correct

  // lines 566-570
  if l > 1 then begin
    l := MultiByteToWideChar(CodePage, MB_PRECOMPOSED, PAnsiChar(@s[1]), -1,
           PWideChar(@w[lResult+1]), l-1);       // buffer size intentionally 
  l-1 (no null)
    Win32Check((l = 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER));
  end;

  The key: the first call determined that l wide chars are needed including the 
  null terminator. The buffer provided to the second call is l-1 — exactly one
  char too small for the null. When cbMultiByte = -1 (null-terminated source)
  and the output buffer is too small, MultiByteToWideChar always returns 0 with
  ERROR_INSUFFICIENT_BUFFER. This is intentional: the code appends to an
  existing wide string w and doesn't want a null in the middle.

  So the second call always returns l = 0 with ERROR_INSUFFICIENT_BUFFER. The
  check Win32Check((l = 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER))
  evaluates to Win32Check(true) → no exception raised. The only time it raises
  is when GetLastError is something other than ERROR_INSUFFICIENT_BUFFER, which
  correctly catches real conversion errors (ERROR_NO_UNICODE_TRANSLATION, etc.).

  The check reads as if it's asserting "the call succeeded" but it's actually
  asserting "the call failed in the expected way." Confusing, yes. A bug in
  practice, no.

  No fix strictly required, but for clarity it could be rewritten as:
  if (l = 0) and (GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
    RaiseLastOSError;

  ---
  Summary

  #: 1
  Finding: GpSharedMemory.pas:1032 Ofs() truncates pointer
  Verdict: Real bug
  Severity: Critical on 64-bit — every memory access wrong
  ────────────────────────────────────────
  #: 2
  Finding: GpSharedMemory.pas:2582 GetEntry truncates pointer
  Verdict: Real bug
  Severity: Critical on 64-bit — index access wrong
  ────────────────────────────────────────
  #: 3
  Finding: GpLists.pas:5150 IndexOf infinite loop
  Verdict: Real bug
  Severity: Critical — hangs any caller with a missing GUID
  ────────────────────────────────────────
  #: 4
  Finding: SpinLock.pas:987 fs: wrong on x64
  Verdict: Real bug
  Severity: Critical on x64 — lock corruption/deadlock
  ────────────────────────────────────────
  #: 5
  Finding: GpStuff.pas:1941 JoinList buffer overrun
  Verdict: Real bug
  Severity: High — heap corruption on every non-empty delimiter
  ────────────────────────────────────────
  #: 6
  Finding: DSiWin32.pas:9304 Wrong proc name in DSiNetApiBufferFree
  Verdict: Real bug
  Severity: High — crash/stack corruption
  ────────────────────────────────────────
  #: 7
  Finding: GpSharedMemory.pas:2951 SizeOf(field) not field
  Verdict: Real bug
  Severity: High — shared-memory index corrupted on resize
  ────────────────────────────────────────
  #: 8
  Finding: DSiWin32.pas:5890 Remove adds ACEs instead of removing
  Verdict: Real bug
  Severity: High — silent security failure
  ────────────────────────────────────────
  #: 9
  Finding: GpSync.pas:1494 ResetEvent race
  Verdict: Not a bug — ResetEvent is correctly called before ReleaseMutex
  Severity: —
  ────────────────────────────────────────
  #: 10
  Finding: GpTextFile.pas:569 Win32Check inverted
  Verdict: Not a practical bug — the second MultiByteToWideChar always fails
  with
    ERROR_INSUFFICIENT_BUFFER by design
  Severity: —

  Eight of ten are confirmed. Fix #1–#8; items #9 and #10 do not need changes.

✻ Baked for 7m 59s

※ recap: Audited the codebase for bugs and verified the top 10 findings. Eight are confirmed real defects. Review the analysis above and decide which to fix first. (disable recaps in /config)