Bug #9572
ArrayIndexOutOfBoundsException and wrong newline handling in RedirectedTerminal.addChar()
100%
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:
- why is this? Is this due to a bug, or to some difference in configuration?
- in which situations the
RedirectedTerminalis 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:
- In OE, the header overwrites the "Decimal: " text, but is indented to be centered on the screen.
- 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
ScreenBitmapthat 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:
- RedirectedTerminal: Implementation updated:
- The
int[] numfield has been removed. - The
char[][] bufferarray and its elements are now nullable. Initially, the buffer isnulland is allocated on-demand per line. The newensureCapacity(x, y)method handles dynamic allocation. Buffer growth is controlled by two new constants:CHAR_BUFFER_INCREMENTandLINE_BUFFER_INCREMENT. - The
CoordinatesConversion conversionfield has been eliminated as it is no longer needed. - The window width is no longer calculated inside the
RedirectedTerminalconstructor; it is now passed as an additionalint widthargument. This breaks the class’s dependency on the complete server-client environment, making unit testing possible. - The
clearAreaJavadocs now explicitly state that thebottomandrightarguments are inclusive indexes. It is unclear if this matches the original intent, but this behavior reflects the current code.
- The
- RedirectedTerminalOld: This is the original version of
RedirectedTerminal, temporarily retained to check for regressions. It will be removed later. - ThinClient: The creation logic for
RedirectedTerminalhas been updated. The window width is now calculated withinThinClient. - NullStream: Minor non-functional updates:
- Added missing
@Overrideannotations. - Removed unnecessary type casts.
These changes are unrelated to the issue and can be safely reverted.
- Added missing
- OutputStringStream: A new class created under the
tests/directory to support JUnit5 unit testing. It extendsStreamand provides minimal functionality to testRedirectedTerminalwith a stream.
- RedirectedTerminalTest: A new JUnit5 test class created in the
tests/directory forRedirectedTerminal. It is configurable to test both the "new" and "old" implementations ofRedirectedTerminal.
Implemented Unit Tests:
testBoxedText- Tests rendering text within a box.testClearArea- Tests clearing a defined area.testClearAreaFar- Tests clearing an area far away from the origin.testLongInput- Tests appending a line longer than the usual terminal width.testMoveOutputToColumn- Tests horizontal shifting of text.testMoveOutputToColumnFar- Tests horizontal shifting of text to a distant column.testReset- Tests terminal output immediately after creation (reset state).testSimple- Tests appending a single character.testWriteAt- Tests writing a character at a specific cursor position.
New Findings:
- The methods
clearAreaandmoveOutputToColumnhave a similar issue: clearing or moving text outside the currently allocated area leads to index out-of-bounds exceptions, causingtestMoveOutputToColumnFarandtestClearAreaFarto fail. - Passing unusual argument combinations to these methods also results in exceptions, leading to failures in
testMoveOutputToColumnandtestClearArea.
Current Fixing Status:
- The main issue has been resolved (
testLongInputnow passes). - In progress:
testClearArea,testMoveOutputToColumn, andtestMoveOutputToColumnFarcontinue to fail due to unresolved issues withclearAreaandmoveOutputToColumn.
#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.
#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
wdthparameter ofdrawHBordertowidth.
It clashed with the field name, it this is a small issue. I can rename it back if you think this is necessary.
syncandviewmethods don't check forbuffer != null. Also should they check fori >= 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.
ensureCapacityignoresxon initial line allocation (newLine = new char[CHAR_BUFFER_INCREMENT]).
You are correct. Fixed.
lengthcalculation inviewdoesn'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, canlastNonBlankIdbe assigned to -1?
This should be OK after the length calculation is fixed.
ScreenBitmapsize will get out of sync with the size of the redirected terminal when it grows its capacity sincebitmapfield 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.
#37 Updated by Hynek Cihlar 11 months ago
Vladimir Tsichevski wrote:
Hynek Cihlar wrote:
ScreenBitmapsize will get out of sync with the size of the redirected terminal when it grows its capacity sincebitmapfield 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.
#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 TOstatement 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 TOstatement 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/
#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
- File TestRedirected.cls added
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.