On Tue, 11 Oct 2022 05:40:40 GMT, Prasanta Sadhukhan <[email protected]>
wrote:
>> javax.swing.text.AbstractDocument$BranchElement.replace(...) method throws
>> an `ArrayIndexOutOfBoundsException: arraycopy: length -1 is negative` when
>> using an UndoManager on the default document of a JTextArea and you try to
>> undo the insertion of a LEFT-TO-RIGHT language (e.g. Arabic) that is
>> immediately followed by setting the component orientation on the JTextArea.
>>
>> This is because System.arrayCopy() is called with -ve length because of the
>> calculation done in AbstractDocment.replace where `src` is of 2bytes because
>> of unicode text causing `nmove` to become -ve if `nchildren` is 1 (an
>> unicode character is inserted)
>>
>> System.arrayCopy throws `IndexOutOfBoundsException if:
>>
>> The srcPos argument is negative.
>> The destPos argument is negative.
>> The length argument is negative
>>
>>
>> so the fix is made to make nmove, src, dest +ve
>> Also, Element.getElement() can return null which is not checked, causing
>> NPE, if deletion of text is done which results in no element, which is also
>> fixed in the PR
>>
>> All jtreg testsuite tests are run without any regression.
>
> Prasanta Sadhukhan has updated the pull request incrementally with one
> additional commit since the last revision:
>
> Test update
This does **corrupt the document structure**.
The negative indexes are the sign that the undoable edit contradicts to the
current document structure and cannot be applied therefore. Ignoring that is a
call for trouble in another place.
What happens in the provided test case?
The document before the Arabic character is inserted:
--- empty ---
<paragraph>
<content>
[0,1][
]
<bidi root>
<bidi level
bidiLevel=0
>
[0,1][
]
This is the document dump obtained via
[`AbstractDocument.dump`](https://docs.oracle.com/en/java/javase/17/docs/api/java.desktop/javax/swing/text/AbstractDocument.html#dump(java.io.PrintStream)).
After the Arabic character is inserted:
--- one char ---
<paragraph>
<content>
[0,2][ر
]
<bidi root>
<bidi level
bidiLevel=1
>
[0,1][ر]
<bidi level
bidiLevel=0
>
[1,2][
]
Now the text component orientation is changed to RTL:
--- orientation changed ---
<paragraph>
<content>
[0,2][ر
]
<bidi root>
<bidi level
bidiLevel=1
>
[0,2][ر
]
The most important change is that bidi-root now contains only one element with
level 1 which contains the entire document content whereas before the
orientation was changed it had contained two elements with level 1 and 0.
The undoable edit stores the data to restore the bidi-root from two leaf
elements to one leaf. Yet now the bidi-root has only one leaf element. That's
why the indexes in `replace` are negative: the undoable edit cannot be applied
correctly.
Let's continue, as the indexes are positive now and the undo can be performed.
--- undone ---
<paragraph>
<content>
[0,1][
]
<bidi root>
This results in bidi-root which has no elements. This is _**not** a valid
document_ any more. I [mentioned
above](https://github.com/openjdk/jdk/pull/10446#discussion_r985670722) that
`bidiElem` in `isLeftToRight` must never be `null`. It *is* `null` because
converting negative indexes to positive in `replace` breaks the structure.
The proposed fix must not be integrated.
Is there a solution? I'm afraid there's none.
The best approach would be to invalidate all the undo information in the
`UndoManager`. *Nothing can be undone* after the orientation of a text
component is changed. This is true even if the edit doesn't throw an exception.
For example, if the document contains the following text: "start
<Arabic-char> end". The insertion of an Arabic character is the last
operation, it will be undone when `undo` is called. The component orientation
was changed to RTL after inserting that Arabic character, which changes the
levels in bidi-root from 0-1-0 to 2-1-2. Calling `undo` undoes the changes and
restores the levels which had been before the orientation was changed, that is
to 0 instead of the expected 2. This results in wrong rendering of
bidirectional text.
I came up with a test case which throws `ArrayIndexOutOfBounds` before the fix
is applied. With the fix applied, it throws an exception on EDT:
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException:
offsetLimit must be after current position
at
java.desktop/java.awt.font.LineBreakMeasurer.nextOffset(LineBreakMeasurer.java:356)
at
java.desktop/javax.swing.text.TextLayoutStrategy.getLimitingOffset(TextLayoutStrategy.java:286)
at
java.desktop/javax.swing.text.TextLayoutStrategy.createView(TextLayoutStrategy.java:194)
at ...
What can we recommend? Set the component orientation *before* editing text and
capturing undoable edits.
-------------
PR: https://git.openjdk.org/jdk/pull/10446