diff --git a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/SBLimitedLength.java b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/SBLimitedLength.java index d478e3cbcc391..51c39e0fa2cba 100644 --- a/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/SBLimitedLength.java +++ b/modules/commons/src/main/java/org/apache/ignite/internal/util/tostring/SBLimitedLength.java @@ -91,6 +91,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(Object obj) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(obj); return this; } @@ -105,6 +106,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(String str) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(str); return this; } @@ -119,6 +121,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(StringBuffer sb) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(sb); return this; } @@ -133,6 +136,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(CharSequence s) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(s); return this; } @@ -147,6 +151,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(CharSequence s, int start, int end) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(s.subSequence(start, end)); return this; } @@ -161,6 +166,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(char[] str) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(str); return this; } @@ -175,6 +181,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(char[] str, int offset, int len) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(Arrays.copyOfRange(str, offset, len)); return this; } @@ -189,6 +196,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(boolean b) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(b); return this; } @@ -203,6 +211,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(char c) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(c); return this; } @@ -217,6 +226,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(int i) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(i); return this; } @@ -231,6 +241,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(long lng) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(lng); return this; } @@ -245,6 +256,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(float f) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(f); return this; } @@ -259,6 +271,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder a(double d) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(d); return this; } @@ -273,6 +286,7 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { /** {@inheritDoc} */ @Override public GridStringBuilder appendCodePoint(int codePoint) { if (lenLimit.overflowed(this)) { + initTailIfAbsent(); tail.append(codePoint); return this; } @@ -311,4 +325,10 @@ private GridStringBuilder onWrite(int lenBeforeWrite) { public boolean isOverflowed() { return lenLimit.overflowed(this); } + + /** */ + private void initTailIfAbsent() { + if (tail == null) + tail = lenLimit.getTail(); + } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/tostring/SBLimitedLengthSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/tostring/SBLimitedLengthSelfTest.java new file mode 100644 index 0000000000000..7c5fd104996c2 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/tostring/SBLimitedLengthSelfTest.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.util.tostring; + +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; +import org.apache.ignite.testframework.junits.common.GridCommonTest; +import org.junit.Test; + +/** Test suite to ensure SBLimitedLength works by design */ +@GridCommonTest(group = "Utils") +public class SBLimitedLengthSelfTest extends GridCommonAbstractTest { + /** Ensure all append operations are working fine */ + @Test + public void testAppend() { + SBLimitedLength strBuilder = stringBuilder(5); + strBuilder.a(1); + assertEquals("1", strBuilder.toString()); + strBuilder.a(2L); + assertEquals("12", strBuilder.toString()); + strBuilder.a(3f); + assertEquals("123.0", strBuilder.toString()); + strBuilder.a(4d); + assertEquals("123.04.0", strBuilder.toString()); + strBuilder.a('5'); + assertEquals("123.04.05", strBuilder.toString()); + strBuilder.a(true); + assertEquals("123.04.05true", strBuilder.toString()); + Object obj = "6"; + strBuilder.a(obj); + assertEquals("123.04.05true6", strBuilder.toString()); + strBuilder.a("7"); + assertEquals("123.04.05true67", strBuilder.toString()); + strBuilder.a(new StringBuilder().append("8")); + assertEquals("123.04.05true678", strBuilder.toString()); + CharSequence charSeq = "9"; + strBuilder.a(charSeq); + assertEquals("123.04.05true6789", strBuilder.toString()); + strBuilder.a(charSeq, 0, 1); + assertEquals("123.04.05true67899", strBuilder.toString()); + } + + /** */ + @Test + public void testDoesNotThrowNPEOnHeadOverflow() { + SBLimitedLength sbLimitedLength = new SBLimitedLength(256); + sbLimitedLength.initLimit(new SBLengthLimit()); + sbLimitedLength.a("a".repeat(7999)); + sbLimitedLength.i(7000, "asd"); + sbLimitedLength.a("a".repeat(10)); + String result = sbLimitedLength.toString(); + assertNotNull(result); + assertFalse(result.isEmpty()); + assertTrue(result.contains("asd")); + } + + /** + * Test that simulates the NPE scenario from handleRecursion. + * When tail is null but overflowed() returns true, append operations should not throw NPE. + */ + @Test + public void testNPEProtectionWithNullTail() { + SBLimitedLength sb = new SBLimitedLength(256); + sb.initLimit(new SBLengthLimit()); + sb.a("a".repeat(8000)); + sb.i(0, "@0"); + sb.a("x"); + } + + /** Ensure toString works as expected */ + @Test + public void testToString() { + SBLimitedLength strBuilder = stringBuilder(2); + strBuilder.a("ab"); + assertEquals("ab", strBuilder.toString()); + strBuilder.a("cd"); + assertEquals("abcd", strBuilder.toString()); + } + + /** + * Get {@link SBLimitedLength} instance with specific head and tail length + * to simplify test cases + * @param headLength Head length. + */ + private SBLimitedLength stringBuilder(int headLength) { + SBLimitedLength sbLimitedLength = new SBLimitedLength(0); + + sbLimitedLength.initLimit(new SBLengthLimit() { + @Override boolean overflowed(SBLimitedLength sb) { + return sb.impl().length() > headLength; + } + }); + + return sbLimitedLength; + } +} + + + diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java index ad2129d4d2e2a..2a75e761ce044 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteUtilSelfTestSuite.java @@ -51,6 +51,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringBuilderSelfTest; import org.apache.ignite.internal.util.tostring.IncludeSensitiveAtomicTest; import org.apache.ignite.internal.util.tostring.IncludeSensitiveTransactionalTest; +import org.apache.ignite.internal.util.tostring.SBLimitedLengthSelfTest; import org.apache.ignite.internal.util.tostring.TransactionSensitiveDataTest; import org.apache.ignite.lang.GridByteArrayListSelfTest; import org.apache.ignite.spi.discovery.ClusterMetricsSelfTest; @@ -92,6 +93,7 @@ GridStringBuilderFactorySelfTest.class, GridToStringBuilderSelfTest.class, CircularStringBuilderSelfTest.class, + SBLimitedLengthSelfTest.class, GridByteArrayListSelfTest.class, GridMBeanSelfTest.class, GridMBeanDisableSelfTest.class,