shashank created CAMEL-26437:
--------------------------------

             Summary: camel-util - FilterIterator.remove after a failed next 
removes an element that next never returned
                 Key: CAMEL-26437
                 URL: https://issues.apache.org/jira/browse/CAMEL-26437
             Project: Camel
          Issue Type: Bug
          Components: camel-core
            Reporter: shashank
            Assignee: shashank


Follow-up of CAMEL-26290 (apache/camel#28643), which made 
{{FilterIterator.remove()}} throw {{IllegalStateException}} after a 
{{hasNext()}} look-ahead, because the look-ahead moves the wrapped iterator 
past the element {{next()}} returned.

{{next()}} looks ahead in the same way when it has no element ready: 
{{checkNext()}} calls {{it.next()}} on the wrapped iterator until the filter 
accepts an element. If that does not end with an accepted element, {{next()}} 
fails, but the permission to remove from the previous successful {{next()}} is 
kept, while the wrapped iterator is now on another element. A {{remove()}} then 
removes that element, which {{next()}} never returned:

{code:java}
List<Integer> list = new ArrayList<>(List.of(1, 2));
FilterIterator<Integer> it = new FilterIterator<>(list.iterator(), n -> n == 1);
it.next();   // 1
it.next();   // looks at 2, rejects it, throws NoSuchElementException
it.remove(); // removes 2; list is now [1]
{code}

The same happens when the filter throws while it looks at the next element, and 
when the elements left are null (they are skipped). The javadoc of {{remove()}} 
says it removes the last element returned by {{next()}}.

Found by an OpenJML/JML verification pilot of the {{FilterIterator}} contract 
(the removal-safety condition failed on the original source and holds with the 
change below), then reproduced with plain JUnit tests.

*Proposal*
* In {{checkNext()}}, set {{canRemove = false}} right before each 
{{it.next()}}: once the wrapped iterator moves, the element returned last can 
no longer be removed. A successful {{next()}} sets it again. A {{next()}} that 
fails at the end of the wrapped iterator, without moving it, keeps the element 
returned before removable (as a list iterator does).
* Tests: {{NoSuchElementException}} after a rejected tail, a throwing filter, a 
skipped null tail, and controls (end of the wrapped iterator, {{hasNext()}} 
before the failed {{next()}}).
* Fold one paragraph into the existing 4.24 upgrade guide entry of CAMEL-26290.

_Filed with Claude Code on behalf of allthingssecurity._



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to