Project

General

Profile

Bug #9572

ArrayIndexOutOfBoundsException and wrong newline handling in RedirectedTerminal.addChar()

Added by Vladimir Tsichevski over 1 year ago. Updated 5 months ago.

Status:
Internal Test
Priority:
Normal
Assignee:
Vladimir Tsichevski
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
15654
version_resolved:
reviewer:
production:
No
env_name:
topics:

TestRedirected.cls (5.06 KB) Vladimir Tsichevski, 02/11/2026 03:38 PM

History

#1 Updated by Vladimir Tsichevski over 1 year ago

  • Subject changed from ArrayIndexOutOfBoundsException in RedirectedTerminal.addChar() to ArrayIndexOutOfBoundsException and wrong newline handling in RedirectedTerminal.addChar()

The buffer in RedirectedTerminal implementation is array of 127 lines by 512 characters.
When a text is appended to the terminal, no position in line bound check is performed, so appending the 513-th character just results with ArrayIndexOutOfBoundsException thrown.

To reproduce:

OUTPUT TO out.
MESSAGE <something longer than 512 characters>.
OUTPUT CLOSE.

Also, the line feed characters do not cause line feed, and just append to the current line, which is wrong.

#2 Updated by Vladimir Tsichevski over 1 year ago

  • Status changed from New to WIP
  • Assignee set to Vladimir Tsichevski

#3 Updated by Vladimir Tsichevski over 1 year ago

In OE the following code:

OUTPUT TO out.
  MESSAGE "Inner".
OUTPUT CLOSE.
MESSAGE "Outer".

Emits only Outer.

In FWD, both Inner and Outer messages are emitted.

My questions are:

  1. why is this? Is this due to a bug, or to some difference in configuration?
  2. in which situations the RedirectedTerminal is really used as a terminal? Can anyone provide me with 4gl example?

#4 Updated by Vladimir Tsichevski over 1 year ago

Branch 9572a created for this task. The RedirectedTerminal refactored in rev. 15768. No functional changes yet.

#5 Updated by Vladimir Tsichevski over 1 year ago

As to how to fix the original problem:

At the moment the sizes of terminal forced as 512 characters by 128 lines. If we emit something bigger to the terminal, then we are in trouble.
My proposal is:

- Create a terminal with virtually unlimited size.
- Use StringBuilder to store a terminal line instead of a char[].
- Use a List[StringBuilder] to store lines instead of an array of char[].
- Allocate lines and characters dynamically on demand.

#6 Updated by Constantin Asofiei over 1 year ago

Vladimir, are you testing in GUI 4GL? In ChUI, both MESSAGE show on screen. Please check if there is some configuration/startup parameter which disables MESSAGE when redirected terminal in GUI.

#7 Updated by Vladimir Tsichevski over 1 year ago

Constantin Asofiei wrote:

Vladimir, are you testing in GUI 4GL? In ChUI, both MESSAGE show on screen.

Yes, I am testing in Swing GUI. And yes, in ChUI more messages are visible.

Please check if there is some configuration/startup parameter which disables MESSAGE when redirected terminal in GUI.

I have not found anything related here:

https://docs.progress.com/bundle/openedge-database-management/page/Startup-Parameters.html

So, I think, this is a bug.

#8 Updated by Vladimir Tsichevski over 1 year ago

I tested with this demo application, and I am confused a bit:

OUTPUT TO 9572.txt.

DEFINE FRAME f
  "Decimal:   "     SKIP
  "Integer:   "     SKIP
  "Char: "          SKIP
  "Date:      "     SKIP
  "Logical:   "     SKIP
  "Value: "         SKIP
  WITH SIZE 45 BY 31 TITLE "Test 9572" 
  .
OUTPUT CLOSE.
MESSAGE "Outer   ".

The 9572.txt file contents in OE:

                Test 9572
Integer:
Char:
Date:
Logical:
Value:

And in FWD:

Test 9572
Decimal:
Integer:
Char:
Date:
Logical:
Value:

Seems, both are not correct:

  1. In OE, the header overwrites the "Decimal: " text, but is indented to be centered on the screen.
  2. In FWD, the header does not overwrite the text, but is not aligned.

I wonder, if the header alignment shall be fixed?

#9 Updated by Vladimir Tsichevski over 1 year ago

There are other issues that require discussion, but I can resolve the origin issue this by allocating enough space to accommodate longer lines.

#10 Updated by Greg Shah over 1 year ago

Please minimize the changes. I don't want to refactor this at this time. I don't want to move to a list approach. Can we just detect the moment we need more space and reallocate the entire set of arrays at a larger size (pick a safe amount)?

So instead of 127 lines by 512 characters, we would have 127 lines by (512 + X) characters where X is some reasonable value that is large enough to hold the data being written.

#11 Updated by Vladimir Tsichevski over 1 year ago

Greg Shah wrote:

Please minimize the changes. I don't want to refactor this at this time. I don't want to move to a list approach. Can we just detect the moment we need more space and reallocate the entire set of arrays at a larger size (pick a safe amount)?

So instead of 127 lines by 512 characters, we would have 127 lines by (512 + X) characters where X is some reasonable value that is large enough to hold the data being written.

We do not need to make all lines the same length, just allocate enough characters on demand for each write character operation.

#12 Updated by Greg Shah over 1 year ago

Hmmm. I think we have dependencies in that code on the assumption that all rows are the same size. And we have external classes like ScreenBitmap that make the same assumption. I don't see a good reason to move away from that approach. It will be a rare case when we must reallocate and the amount of memory used is not enough to justify making the code more complicated.

#13 Updated by Vladimir Tsichevski over 1 year ago

Greg Shah wrote:

Hmmm. I think we have dependencies in that code on the assumption that all rows are the same size.

I haven't found any evidence for this in the current implementation of the RedirectedTerminal code.

And we have external classes like ScreenBitmap that make the same assumption.

Yes, but IMO, this is unrelated to the current issue, as the bitmap isn't used within the existing RedirectedTerminal code.

I don't see a good reason to move away from that approach. It will be a rare case when we must reallocate and the amount of memory used is not enough to justify making the code more complicated.

We must significantly increase the buffer size (approximately doubling it) upon each reallocation; otherwise, we'll end up reallocating on every character.

Also, we have the following implementation for clean:

   /**
    * Wipe the slate clean (initialize our state to its pristine condition).
    */
   private void clear()
   {
      Arrays.fill(num, 0);
      Arrays.fill(box, false);

      for (int i = 0; i < lines; i++)
      {
         Arrays.fill(buffer[i], ' ');
      }

      ...

Here we fill the entire terminal area with spaces, even if we change just one character. This can happen frequently (e.g., after every MESSAGE call). I'd prefer to allocate space dynamically as needed to avoid unnecessary operations, and never clear anything past n[cy].

#14 Updated by Vladimir Tsichevski over 1 year ago

I also need to fix the clearArea. The meaning of the right and bottom arguments is unclear to me: are these indices inclusive or exclusive?

The Javadocs do not provide any hint. However, based on the implementation, it appears that: right is treated as exclusive, and bottom is treated as inclusive.

#15 Updated by Greg Shah over 1 year ago

We must significantly increase the buffer size (approximately doubling it) upon each reallocation; otherwise, we'll end up reallocating on every character.

Doubling seems excessive. Use some fixed "chunk" size like 128 characters and increase it by the number of chunks needed to contain the new larger line.

With this change, we can also start the terminal size smaller since most terminals will fit inside less than 140 characters width.

#16 Updated by Vladimir Tsichevski over 1 year ago

Greg Shah wrote:

We must significantly increase the buffer size (approximately doubling it) upon each reallocation; otherwise, we'll end up reallocating on every character.

Doubling seems excessive.

Use some fixed "chunk" size like 128 characters and increase it by the number of chunks needed to contain the new larger line.

No objection.

With this change, we can also start the terminal size smaller since most terminals will fit inside less than 140 characters width.

We can start from size 0 easily, and do not pre-allocate anything at all.

#17 Updated by Vladimir Tsichevski over 1 year ago

Question about getScreenBitmap / setScreenBitmap:

What is the purpose of the bitmap? It does not appear to be used inside RedirectedTerminal, and RedirectedTerminal provides no way to access its contents other than dumping it to a stream.

If the bitmap is not actually used, can we assign arbitrary fixed dimensions? Currently, it is set to 512x128, which already seems arbitrary.

#18 Updated by Vladimir Tsichevski over 1 year ago

  • % Done changed from 0 to 50
  • reviewer Greg Shah added

The 9572a rev. 15769 contains some intermediate state for early review:

Change Description:

  1. RedirectedTerminal: Implementation updated:
    1. The int[] num field has been removed.
    2. The char[][] buffer array and its elements are now nullable. Initially, the buffer is null and is allocated on-demand per line. The new ensureCapacity(x, y) method handles dynamic allocation. Buffer growth is controlled by two new constants: CHAR_BUFFER_INCREMENT and LINE_BUFFER_INCREMENT.
    3. The CoordinatesConversion conversion field has been eliminated as it is no longer needed.
    4. The window width is no longer calculated inside the RedirectedTerminal constructor; it is now passed as an additional int width argument. This breaks the class’s dependency on the complete server-client environment, making unit testing possible.
    5. The clearArea Javadocs now explicitly state that the bottom and right arguments are inclusive indexes. It is unclear if this matches the original intent, but this behavior reflects the current code.
  2. RedirectedTerminalOld: This is the original version of RedirectedTerminal, temporarily retained to check for regressions. It will be removed later.
  3. ThinClient: The creation logic for RedirectedTerminal has been updated. The window width is now calculated within ThinClient.
  4. NullStream: Minor non-functional updates:
    1. Added missing @Override annotations.
    2. Removed unnecessary type casts.

    These changes are unrelated to the issue and can be safely reverted.

  5. OutputStringStream: A new class created under the tests/ directory to support JUnit5 unit testing. It extends Stream and provides minimal functionality to test RedirectedTerminal with a stream.
  1. RedirectedTerminalTest: A new JUnit5 test class created in the tests/ directory for RedirectedTerminal. It is configurable to test both the "new" and "old" implementations of RedirectedTerminal.

Implemented Unit Tests:

  1. testBoxedText - Tests rendering text within a box.
  2. testClearArea - Tests clearing a defined area.
  3. testClearAreaFar - Tests clearing an area far away from the origin.
  4. testLongInput - Tests appending a line longer than the usual terminal width.
  5. testMoveOutputToColumn - Tests horizontal shifting of text.
  6. testMoveOutputToColumnFar - Tests horizontal shifting of text to a distant column.
  7. testReset - Tests terminal output immediately after creation (reset state).
  8. testSimple - Tests appending a single character.
  9. testWriteAt - Tests writing a character at a specific cursor position.

New Findings:

  1. The methods clearArea and moveOutputToColumn have a similar issue: clearing or moving text outside the currently allocated area leads to index out-of-bounds exceptions, causing testMoveOutputToColumnFar and testClearAreaFar to fail.
  2. Passing unusual argument combinations to these methods also results in exceptions, leading to failures in testMoveOutputToColumn and testClearArea.

Current Fixing Status:

  1. The main issue has been resolved (testLongInput now passes).
  2. In progress: testClearArea, testMoveOutputToColumn, and testMoveOutputToColumnFar continue to fail due to unresolved issues with clearArea and moveOutputToColumn.

#19 Updated by Greg Shah over 1 year ago

  • reviewer Hynek Cihlar added
  • reviewer deleted (Greg Shah)

#20 Updated by Greg Shah over 1 year ago

Why didn't you check in the unit tests to 9572a? If they were useful for this task then they should be checked in.

#21 Updated by Vladimir Tsichevski over 1 year ago

  • reviewer Greg Shah added
  • reviewer deleted (Hynek Cihlar)

Greg Shah wrote:

Why didn't you check in the unit tests to 9572a? If they were useful for this task then they should be checked in.

Hmmm. They must be located here:

test/com/goldencode/p2j/ui/chui/OutputStringStream.java
test/com/goldencode/p2j/ui/chui/RedirectedTerminalTest.java

As to 4GL tests, I currently have a few and plan to upload them to a location within the testcases. As usual, I’m uncertain about the exact location.
These tests reveal that FWD behaves differently from the original OE. However, this is most likely not related to the RedirectedTerminal implementation.

#22 Updated by Vladimir Tsichevski over 1 year ago

  • reviewer Hynek Cihlar added
  • reviewer deleted (Greg Shah)

#23 Updated by Greg Shah over 1 year ago

Sorry, you're right. I missed the test files.

You can ask Marian if you need help in finding the right spot for the 4GL code.

#24 Updated by Hynek Cihlar over 1 year ago

Early review 9572a.

In drawVBorder, shouldn't the previous state be copied to the new re-allocated array?

Otherwise awful lot of unrelated and non-functional changes for such a trivial issue. Very hard to spot the effective changes. Vladimir, if you need to do non-functional refactoring changes, can you please at least put them in separate checkins?

#25 Updated by Vladimir Tsichevski over 1 year ago

Hynek Cihlar wrote:

Early review 9572a.

Otherwise awful lot of unrelated and non-functional changes for such a trivial issue. Very hard to spot the effective changes. Vladimir, if you need to do non-functional refactoring changes, can you please at least put them in separate checkins?

The non-functional refactorings in RedirectedTerminal were made in the previous separate checkin in rev. 15768. Please, see the changes 15768-15796 for the functional changes.

#26 Updated by Vladimir Tsichevski over 1 year ago

Hynek Cihlar wrote:

Early review 9572a.

In drawVBorder, shouldn't the previous state be copied to the new re-allocated array?

You are right. Just fixed this, will commit the change soon.

#27 Updated by Vladimir Tsichevski over 1 year ago

I am fixing the moveOutputToColumn(final int fromCol, final int toCol), currently implemented as:

   public void moveOutputToColumn(int fromCol, int toCol)
   {
      int firstRow = this.cy;
      int lastRow  = this.last;
      for (int i = firstRow; i <= lastRow; i++)
      {
         if (num[i] > 0)
         {
            char[] row = Arrays.copyOf(buffer[i], buffer[i].length);

            Arrays.fill(buffer[i], ' ');
            System.arraycopy(row, fromCol, buffer[i], toCol, num[i]);
            num[i] = num[i] - (fromCol - toCol);
         }
      }
   }

From the code, it appears that this method shifts the contents of lines within the range from cy to last. The cy value corresponds to the line affected by the last terminal drawing operation. However, since this method is called from a location that performs no drawing, is it possible that cy could hold an arbitrary value here?

Also, without any assumptions about the arguments, the method does not include protections against overflowing the line boundaries.

#28 Updated by Vladimir Tsichevski over 1 year ago

The breakable field's lifecycle is unclear. It maintains a set of line numbers that can only grow; the set is never cleared, even after a terminal reset. Is this the expected behavior?

#29 Updated by Vladimir Tsichevski over 1 year ago

  • % Done changed from 50 to 90
  • Status changed from WIP to Review

9572a committed as rev. 15770, please do the next intermediate review.

RedirectedTerminal:

- Javadocs for most fields have been clarified, explicitly defining whether parameter indexes are inclusive or exclusive.
- Methods such as clearArea(), drawVBorder(), and moveOutputToColumn() now accept any combination of integer parameters without throwing exceptions.
- A new utility method, lastNonBlankIdx(), has been introduced.
- Fixed an incorrect y allocation size calculation in ensureCapacity().

RedirectedTerminalTest:

- Added multiple tests, primarily focusing on corner cases.

The original terminal implementation passes all unit tests except those triggering out-of-bounds issues. Using the same expected results, the new implementation now passes all tests, ensuring no regressions.

Next steps:

Once the review is complete, I will remove RedirectedTerminalOld, as its only purpose was to support regression testing.

#31 Updated by Vladimir Tsichevski 11 months ago

I've rebased 9572a to rev. 16118. Please, review.

#32 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from Review to WIP

I've rebased 9572a to rev. 16122, and it ceased to work.

#33 Updated by Vladimir Tsichevski 11 months ago

  • Status changed from WIP to Review

Vladimir Tsichevski wrote:

I've rebased 9572a to rev. 16122, and it ceased to work.

It was false alarm.

#34 Updated by Hynek Cihlar 11 months ago

  • Status changed from Review to WIP

Code review 9572a.

   /**
    * Current logical width of terminal.
    * This field value has no impact on the terminal's functionality whatsoever.
    */
   private int width;

The javadoc is misleading, the width is used to allocate the buffer width, default window width, etc.

Please rename back wdth parameter of drawHBorder to width.

sync and view methods don't check for buffer != null. Also should they check for i >= buffer.length?

ensureCapacity ignores x on initial line allocation (newLine = new char[CHAR_BUFFER_INCREMENT]).

length calculation in view doesn't seem right, it is assigned an index of the last non-space char.

txt = new String(line, boxOffset, lastNonBlankIdx + 1 - boxOffset); is suspicious, can lastNonBlankId be assigned to -1?

ScreenBitmap size will get out of sync with the size of the redirected terminal when it grows its capacity since bitmap field is assigned.

#35 Updated by Vladimir Tsichevski 11 months ago

Hynek Cihlar wrote:

Code review 9572a.

[...]
The javadoc is misleading, the width is used to allocate the buffer width, default window width, etc.

This value is not used in the RedirectedTerminal code. It can be set from outside and retrieved back. I can update the description if this is misleading. How should it read, by your opinion?

Please rename back wdth parameter of drawHBorder to width.

It clashed with the field name, it this is a small issue. I can rename it back if you think this is necessary.

sync and view methods don't check for buffer != null. Also should they check for i >= buffer.length?

You are correct, there can be situations, when the buffer is accessed in null state. I introduced the getRow(int) method to encapsulate read access to buffer.

ensureCapacity ignores x on initial line allocation (newLine = new char[CHAR_BUFFER_INCREMENT]).

You are correct. Fixed.

length calculation in view doesn't seem right, it is assigned an index of the last non-space char.

You are right. Fixed. Also optimized by using the existing lastNonBlankIdx() methos. Also added the testView() test method to test the view() method.

txt = new String(line, boxOffset, lastNonBlankIdx + 1 - boxOffset); is suspicious, can lastNonBlankId be assigned to -1?

This should be OK after the length calculation is fixed.

ScreenBitmap size will get out of sync with the size of the redirected terminal when it grows its capacity since bitmap field is assigned.

I conclude that the @ScreenBitmap* is determined by the terminal's width and height, which remain unaffected by terminal output.

See the fixes in rev. 16123.

#36 Updated by Vladimir Tsichevski 11 months ago

  • % Done changed from 90 to 100
  • Status changed from WIP to Review

#37 Updated by Hynek Cihlar 11 months ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

ScreenBitmap size will get out of sync with the size of the redirected terminal when it grows its capacity since bitmap field is assigned.

I conclude that the @ScreenBitmap* is determined by the terminal's width and height, which remain unaffected by terminal output.

Right, the screen bitmap should only be affected by the screen size and not the buffer size.

#38 Updated by Hynek Cihlar 11 months ago

  • Status changed from Review to Internal Test

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

#39 Updated by Hynek Cihlar 11 months ago

Hynek Cihlar wrote:

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

ChUI regression tests passed.

#40 Updated by Vladimir Tsichevski 11 months ago

Hynek Cihlar wrote:

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

Could you propose customer scenarios associated with the redirected terminal?

#41 Updated by Hynek Cihlar 11 months ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

Could you propose customer scenarios associated with the redirected terminal?

Search for the OUTPUT TO statement in Hotel GUI and the testcases projects, or the other customer apps, if you will find them relevant.

#42 Updated by Vladimir Tsichevski 10 months ago

Hynek Cihlar wrote:

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

Could you propose customer scenarios associated with the redirected terminal?

Search for the OUTPUT TO statement in Hotel GUI and the testcases projects, or the other customer apps, if you will find them relevant.

I evaluated the Hotel application, including the export HTML operation, and the results were OK. Regarding the testcases, I could not locate a test catalog that builds without encountering multiple conversion and compilation errors. Are you aware of any such test catalogs?

#43 Updated by Hynek Cihlar 10 months ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Code review 9572a. Please start regression testing. I suppose I will run the ChUI regression tests.

Could you propose customer scenarios associated with the redirected terminal?

Search for the OUTPUT TO statement in Hotel GUI and the testcases projects, or the other customer apps, if you will find them relevant.

I evaluated the Hotel application, including the export HTML operation, and the results were OK. Regarding the testcases, I could not locate a test catalog that builds without encountering multiple conversion and compilation errors. Are you aware of any such test catalogs?

Try the old testcases project, there are many *redirected*.p test cases.

#44 Updated by Hynek Cihlar 5 months ago

Vladimir, what is the status of this issue?

#45 Updated by Vladimir Tsichevski 5 months ago

Hynek Cihlar wrote:

Vladimir, what is the status of this issue?

You suggested:

Try the old testcases project, there are many redirected.p test cases.

I have not found files matching this pattern in the xfertestcases project (xfer.goldencode.com/opt/testcases/).

#46 Updated by Hynek Cihlar 5 months ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Vladimir, what is the status of this issue?

You suggested:

Try the old testcases project, there are many redirected.p test cases.

I have not found files matching this pattern in the xfertestcases project (xfer.goldencode.com/opt/testcases/).

~/secure/code/p2j_repo/testcases/

#47 Updated by Vladimir Tsichevski 5 months ago

Hynek Cihlar wrote:

Try the old testcases project, there are many redirected.p test cases.

There are about 170 matching test files - I don't know where to start :-)
It would be good to rewrite them as ABLUnit tests first.

#48 Updated by Hynek Cihlar 5 months ago

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Try the old testcases project, there are many redirected.p test cases.

There are about 170 matching test files - I don't know where to start :-)

Pick some representative subset.

#49 Updated by Vladimir Tsichevski 5 months ago

Hynek Cihlar wrote:

Vladimir Tsichevski wrote:

Hynek Cihlar wrote:

Try the old testcases project, there are many redirected.p test cases.

There are about 170 matching test files - I don't know where to start :-)

Pick some representative subset.

I converted some of the procedures to ABLUnit test class TestRedirected.cls and ran it with trunk and 9572a (rebased to the latest trunk).
The pass/fail pattern is the same, so I see no regressions.

Also available in: Atom PDF