[ 
https://issues.apache.org/jira/browse/COMMONSSITE-177?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Gary D. Gregory deleted COMMONSSITE-177:
----------------------------------------


> [Apache Commons CLI] HelpFormatter infinite loop when width=0
> -------------------------------------------------------------
>
>                 Key: COMMONSSITE-177
>                 URL: https://issues.apache.org/jira/browse/COMMONSSITE-177
>             Project: Apache Commons All
>          Issue Type: Bug
>            Reporter: Ruiqi Dong
>            Priority: Critical
>   Original Estimate: 0.5h
>  Remaining Estimate: 0.5h
>
> I am writing to report an infinite loop bug in the HelpFormatter class when 
> the width parameter is set to 0. Despite existing protective code meant to 
> prevent this issue, the infinite loop still occurs under certain conditions.
>  
> *Problem Description:*
> When calling HelpFormatter.printOptions() or any method that uses 
> appendWrappedText() with width=0, the application enters an infinite loop, 
> eventually causing heap space exhaustion.
>  
>  
> *Test Cases:*
> public class HelpFormatterTest {
> @Test
> public void {*}shouldHandleZeroWidthGracefully{*}() {
> HelpFormatter formatter = new HelpFormatter();
> Options options = new Options();
> options.addOption("h", "help", false, "Show help");
> StringWriter out = new StringWriter();
> PrintWriter pw = new PrintWriter(out);
> _// execute - this will trigger an infinite loop, but will be interrupted by 
> @Timeout annotation_
> try {
> formatter.printOptions(pw, 0, options, 1, 3);
> _// if we get here, it means there was no infinite loop (expected behavior 
> after fix)_
> String result = out.toString();
> assertNotNull(result, "Result should not be null after fix");
> } catch (Throwable {_}t{_}) {
> _// before the fix, this would catch the timeout exception_
> fail("printOptions with width=0 should not cause infinite loop or throw 
> exception");
> }
> }
> @Test
> public void {*}demonstrateInfiniteLoopMechanism{*}() {
> HelpFormatter formatter = new HelpFormatter();
> _// directly test findWrapPos method to demonstrate the root cause_
> String text = "Hello World";
> int pos = formatter.findWrapPos(text, 0, 0);
> _// verify: when width=0, findWrapPos returns 0 (for non-empty text)_
> assertEquals(0, pos, "findWrapPos with width=0 returns 0, causing infinite 
> loop");
> _// this means the text will never be consumed, because substring(0) returns 
> the entire string_
> }
> }
>  
>  
> *Test Results:*
> [{*}INFO{*}] Running org.apache.commons.cli.{*}HelpFormatterTest{*}
> [{*}ERROR{*}] {*}Tests run: 2{*}, {*}Failures: 1{*}, Errors: 0, Skipped: 0, 
> Time elapsed: 25.60 s *<<< FAILURE!* -- in 
> org.apache.commons.cli.{*}HelpFormatterTest{*}
> [{*}ERROR{*}] 
> org.apache.commons.cli.HelpFormatterTest.shouldHandleZeroWidthGracefully -- 
> Time elapsed: 25.60 s <<< FAILURE!
> org.opentest4j.AssertionFailedError: printOptions with width=0 should not 
> cause infinite loop or throw exception
> at org.junit.jupiter.api.AssertionUtils.fail(AssertionUtils.java:38)
> at org.junit.jupiter.api.Assertions.fail(Assertions.java:138)
> at 
> org.apache.commons.cli.HelpFormatterTest.shouldHandleZeroWidthGracefully(HelpFormatterTest.java:54)
> at java.lang.reflect.Method.invoke(Method.java:498)
> at java.util.ArrayList.forEach(ArrayList.java:1259)
> at java.util.ArrayList.forEach(ArrayList.java:1259)
>  
>  
> *Root Cause Analysis:*
> I've identified the exact mechanism causing the infinite loop:
> 1. `findWrapPos(text, 0, 0)` returns 0 for non-empty text (confirmed by my 
> test)
> 2. In `appendWrappedText()` at line 504: `pos = findWrapPos(render, width, 
> 0);`
> 3. When pos=0, line 526: `appendable.append(rtrim(render.substring(0, pos)))` 
> appends empty string
> 4. Line 509 adds a newline
> 5. Lines 510-512: The protective code sets `nextLineTabStopPos = 1` when 
> `nextLineTabStopPos >= width`
> 6. However, line 517: `render = padding + render.substring(pos).trim();` 
>    - Since pos=0, `render.substring(0)` returns the entire string
>    - The text is never consumed, so the loop continues infinitely
>  
> The protective code (lines 510-513) doesn't help because:
> - It only adjusts `nextLineTabStopPos` 
> - It doesn't address that `pos` remains 0, causing text to never be consumed
> - Each iteration adds a newline and creates new String objects, eventually 
> causing heap exhaustion
>  
>  
>  
> *Current Code (HelpFormatter.java, lines 501-528):*
> <A extends Appendable> A appendWrappedText(final A appendable, final int 
> width, final int nextLineTabStop, final String text) throws IOException {
>         String render = text;
>         int nextLineTabStopPos = nextLineTabStop;
>         int pos = findWrapPos(render, width, 0);
>         if (pos == -1) {
>             appendable.append(rtrim(render));
>             return appendable;
>         }
>         appendable.append(rtrim(render.substring(0, 
> pos))).append(getNewLine());
>         if (nextLineTabStopPos >= width) {
>             // stops infinite loop happening
>             nextLineTabStopPos = 1;
>         }
>         // all following lines must be padded with nextLineTabStop space 
> characters
>         final String padding = createPadding(nextLineTabStopPos);
>         while (true) {
>             render = padding + render.substring(pos).trim();
>             pos = findWrapPos(render, width, 0);
>             if (pos == -1) {
>                 appendable.append(render);
>                 return appendable;
>             }
>             if (render.length() > width && pos == nextLineTabStopPos - 1) {
>                 pos = width;
>             }
>             appendable.append(rtrim(render.substring(0, 
> pos))).append(getNewLine());
>         }
>     }
>  
>  
> *Why the Existing Protection Fails:*
> The comment "stops infinite loop happening" suggests awareness of this issue, 
> but the fix only addresses `nextLineTabStopPos`, not the core problem that 
> `findWrapPos` returns 0 when width=0, preventing text consumption.
>  
>  
> *Proposed Solutions:*
> 1. Add width validation (recommended):
> <A extends Appendable> A appendWrappedText(final A appendable, final int 
> width, 
>                                            final int nextLineTabStop, final 
> String text) throws IOException {
>     if (width <= 0) {
>         throw new IllegalArgumentException("Width must be positive, was: " + 
> width);
>     }
>     // existing code...
> }
>  
> 2. Handle width=0 as special case:
> if (width <= 0) {
>     appendable.append(text);
>     return appendable;
> }
>  
> 3. Ensure minimum effective width:
> int effectiveWidth = Math.max(width, 1);
> int pos = findWrapPos(render, effectiveWidth, 0);
>  
> *Impact:*
> - Any application passing width=0 to HelpFormatter will experience heap 
> exhaustion
> - Current protective code is insufficient for the width=0 edge case



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to