EgorBaranovEnjoysTyping commented on PR #13262:
URL: https://github.com/apache/ignite/pull/13262#issuecomment-4830824180

       Fix Summary
   
       1. NPE in handleRecursion
       Root cause: handleRecursion(SBLimitedLength buf, Object obj, Class cls, 
IdentityHashMap<Object, EntryReference> svdObjs) accesses 
buf.impl().substring(pos, pos + savedName.length()) where pos comes from
       EntryReference.pos. When an object is encountered for the first time, 
svdObjs.get(obj) returns null, but the method returns early. However, when the 
buffer is shared across nested toString() calls (via
       ThreadLocal<SBLimitedLength>), the pos stored in EntryReference can 
point to an invalid offset after the buffer is reset or reused, causing 
NullPointerException when buf.impl() returns null.
   
       Fix: Replaced the mutable buffer-based approach with a tree-of-nodes 
architecture (GridToStringNode). Recursion is tracked via NodeRecursionMonitor 
(ThreadLocal registry), and each node appends itself
       independently — no shared mutable buffer, no position tracking.
   
       ---
   
       2. Inconsistent identity hash assignment
       Root cause: In the original code, the identity hash of an object was 
only inserted into the buffer at a previously recorded position (buf.i(pos + 
name.length(), hash)). When toString() was called directly
       on an object (top-level), ref.hashNeeded was set to true, but the hash 
was never appended because there was no prior occurrence at a known position. 
However, when the same object appeared nested inside
       another object's toString(), the hash was inserted for both the 
recursive termination node and the original occurrence. This resulted in 
inconsistent output: the top-level object had no hash, while nested
       objects did.
   
       Fix: The new GridToStringRecursionTerminationNode immediately marks the 
parent NodeRecursionMonitor with setHashRequired(), and the parent node appends 
its hash during appendNode() via
       appendHashIfRequired(sb). This ensures consistent hash output regardless 
of call depth.
   
       ---
   
       3. StringIndexOutOfBoundsException in handleRecursion
       Root cause: handleRecursion calculates charsAtPos = 
buf.impl().substring(pos, pos + savedName.length()) where savedName = 
cls.getSimpleName() + identity(obj). If pos + savedName.length() exceeds the
       current buffer length, substring() throws 
StringIndexOutOfBoundsException. This happens when the buffer has grown past 
pos but not enough to contain the full savedName string at that offset, or when 
the
       buffer was truncated due to the head limit.
   
       Fix: Eliminated position-based buffer manipulation entirely. The node 
tree approach builds the string representation by traversing nodes, so no 
substring operations at arbitrary positions are needed.
   
       ---
   
       4. Incorrect string truncation at the head-tail boundary in SBLengthLimit
       Root cause: The original onWrite method used Math.min(sb.length(), 
HEAD_LEN + 1) to determine the head length when creating the tail. The 
overflowed() check used sb.impl().length() > HEAD_LEN (strict
       greater-than), which caused an off-by-one error: the head could exceed 
HEAD_LEN by one character before the tail was created. Additionally, 
sb.setLength(newSbLen) was called, but the parent class's
       setLength behavior didn't account for the tail split correctly, 
resulting in characters being lost or duplicated at the boundary.
   
       Fix: Changed overflowed() to use >= (sb.length() >= 
getHeadLengthLimit()), replaced HEAD_LEN + 1 with getHeadLengthLimit(), and 
used sb.impl().setLength() directly to avoid interference from overridden
       methods. Added length() override in SBLimitedLength that correctly 
accounts for tail.getSkipped() + tail.length().
   
       ---
   
       5. Missing insert() methods in SBLimitedLength and CircularStringBuilder
       Root cause: SBLimitedLength didn't override the insert() family of 
methods from GridStringBuilder. When handleRecursion called buf.i(pos + 
name.length(), hash) to insert the identity hash, the parent
       class's insert() operated on the underlying buffer without respecting 
the head-tail split, corrupting the output or causing 
StringIndexOutOfBoundsException.
   
       Fix: Implemented i(int offset, String str) in SBLimitedLength that 
correctly redirects inserts to the tail when the offset is beyond the head 
limit. Implemented insert(int offset, String valToInsert) in
       CircularStringBuilder with proper circular buffer handling. Methods that 
delete/modify the head (d(), r(), setLength()) explicitly throw 
UnsupportedOperationException.


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to