8173111: Excessive recursion in EventFilterSupport when filtering over large number of XML events can cause StackOverflow
This fix replaces un unwanted recursion in the XML streams event filtering support by a simple loop (in nextEvent/nextTag). Reviewed-by: aefimov, clanger, lancea, rriggs
This commit is contained in:
parent
afcf1eb292
commit
40232913c4
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.
|
* Copyright (c) 2005, 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -64,7 +64,7 @@ public class EventFilterSupport extends EventReaderDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public XMLEvent nextEvent()throws XMLStreamException{
|
public XMLEvent nextEvent()throws XMLStreamException{
|
||||||
if(super.hasNext()){
|
while (super.hasNext()) {
|
||||||
//get the next event by calling XMLEventReader
|
//get the next event by calling XMLEventReader
|
||||||
XMLEvent event = super.nextEvent();
|
XMLEvent event = super.nextEvent();
|
||||||
|
|
||||||
@ -72,27 +72,19 @@ public class EventFilterSupport extends EventReaderDelegate {
|
|||||||
if(fEventFilter.accept(event)){
|
if(fEventFilter.accept(event)){
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
return nextEvent();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
}
|
||||||
|
throw new NoSuchElementException();
|
||||||
}//nextEvent()
|
}//nextEvent()
|
||||||
|
|
||||||
public XMLEvent nextTag() throws XMLStreamException{
|
public XMLEvent nextTag() throws XMLStreamException{
|
||||||
if(super.hasNext()){
|
while (super.hasNext()) {
|
||||||
XMLEvent event = super.nextTag();
|
XMLEvent event = super.nextTag();
|
||||||
//if the filter accepts this event return this event.
|
//if the filter accepts this event return this event.
|
||||||
if(fEventFilter.accept(event)){
|
if(fEventFilter.accept(event)){
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
else{
|
|
||||||
return nextTag();
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
}
|
||||||
|
throw new NoSuchElementException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public XMLEvent peek() throws XMLStreamException{
|
public XMLEvent peek() throws XMLStreamException{
|
||||||
|
@ -0,0 +1,221 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package stream.EventsTest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import javax.xml.stream.EventFilter;
|
||||||
|
import javax.xml.stream.XMLEventReader;
|
||||||
|
import javax.xml.stream.XMLInputFactory;
|
||||||
|
import javax.xml.stream.XMLStreamException;
|
||||||
|
import javax.xml.stream.events.XMLEvent;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static org.testng.Assert.assertEquals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8173111
|
||||||
|
* @summary tests that filtering out nested elements doesn't end up in
|
||||||
|
* a StackOverflowException
|
||||||
|
* @run testng/othervm stream.EventsTest.EventFilterSupportTest
|
||||||
|
* @author danielfuchs
|
||||||
|
*/
|
||||||
|
public class EventFilterSupportTest {
|
||||||
|
static final String ROOT = "xml";
|
||||||
|
static final String NEXT = "foo";
|
||||||
|
static final String SMOKE = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>"
|
||||||
|
+ "<xml><foo><foo><foo></foo></foo></foo></xml>";
|
||||||
|
// A number high enough to trigger StackOverflowException before the fix.
|
||||||
|
static final int MAX = 100_000;
|
||||||
|
|
||||||
|
public static void main(String[] args)
|
||||||
|
throws XMLStreamException, IOException {
|
||||||
|
smokeTest();
|
||||||
|
testNextEvent(MAX);
|
||||||
|
testNextTag(MAX);
|
||||||
|
System.out.println("Tests passed...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The smoke test just verifies that our TestInputStream works as
|
||||||
|
// expected and produces the expected stream of characters.
|
||||||
|
// Here we test it with 4 nested elements.
|
||||||
|
@Test
|
||||||
|
public static void smokeTest() throws IOException {
|
||||||
|
System.out.println("\nSmoke test...");
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (InputStream ts = new TestInputStream(4)) {
|
||||||
|
int c;
|
||||||
|
while ((c = ts.read()) != -1) {
|
||||||
|
System.out.print((char)c);
|
||||||
|
sb.append((char)c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(sb.toString(), SMOKE, "Smoke test failed");
|
||||||
|
System.out.println("\nSmoke test passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test calling XMLEventReader.nextEvent()
|
||||||
|
@Test
|
||||||
|
public static void testNextEvent() throws IOException, XMLStreamException {
|
||||||
|
testNextEvent(MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without the fix, will cause a StackOverflowException if 'max' is high
|
||||||
|
// enough
|
||||||
|
private static void testNextEvent(int max)
|
||||||
|
throws IOException, XMLStreamException {
|
||||||
|
System.out.println("\nTest nextEvent (" + max + ")...");
|
||||||
|
XMLEventReader reader = createXmlReader(max);
|
||||||
|
XMLEvent event;
|
||||||
|
do {
|
||||||
|
event = reader.nextEvent();
|
||||||
|
System.out.println(event);
|
||||||
|
} while (event.getEventType() != XMLEvent.END_DOCUMENT);
|
||||||
|
System.out.println("nextEvent passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test calling XMLEventReader.nextTag()
|
||||||
|
@Test
|
||||||
|
public static void testNextTag() throws IOException, XMLStreamException {
|
||||||
|
testNextTag(MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without the fix, will cause a StackOverflowException if 'max' is high
|
||||||
|
// enough
|
||||||
|
private static void testNextTag(int max)
|
||||||
|
throws IOException, XMLStreamException {
|
||||||
|
System.out.println("\nTest nextTag (" + max + ")...");
|
||||||
|
XMLEventReader reader = createXmlReader(max);
|
||||||
|
XMLEvent event;
|
||||||
|
do {
|
||||||
|
event = reader.nextTag();
|
||||||
|
System.out.println(event);
|
||||||
|
if (event.getEventType() == XMLEvent.END_ELEMENT
|
||||||
|
&& event.asEndElement().getName().getLocalPart().equals(ROOT)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (true);
|
||||||
|
System.out.println("nextTag passed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XMLEventReader createXmlReader(int max)
|
||||||
|
throws XMLStreamException {
|
||||||
|
TestInputStream ts = new TestInputStream(max);
|
||||||
|
XMLInputFactory xif = XMLInputFactory.newInstance();
|
||||||
|
XMLEventReader reader = xif.createXMLEventReader(ts);
|
||||||
|
return xif.createFilteredReader(reader, new TagFilter(max));
|
||||||
|
}
|
||||||
|
|
||||||
|
// An input stream that pretends to contain 'max - 1' nested NEXT tags
|
||||||
|
// within a ROOT element:
|
||||||
|
// <?xml version="1.0" encoding="US-ASCII"?>
|
||||||
|
// <ROOT><NEXT><NEXT>...</NEXT></NEXT></ROOT>
|
||||||
|
// (1 ROOT element + max-1 nested NEXT elements)
|
||||||
|
public static class TestInputStream extends InputStream {
|
||||||
|
|
||||||
|
int open = 0;
|
||||||
|
int i = 0;
|
||||||
|
int n = 0;
|
||||||
|
final int max;
|
||||||
|
|
||||||
|
public TestInputStream(int max) {
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
String tag() {
|
||||||
|
if (n == 0) {
|
||||||
|
// opening first element - includes the XML processing instruction.
|
||||||
|
return "?xml version=\"1.0\" encoding=\"US-ASCII\"?><" + ROOT;
|
||||||
|
}
|
||||||
|
if (n == 2 * max -1) {
|
||||||
|
// closing the first element
|
||||||
|
// we have 'max' opening tags (0..max-1) followed by
|
||||||
|
// 'max' closing tags (max..2*max-1)
|
||||||
|
// for n in [0..max-1] we open the tags,
|
||||||
|
// for n in [max..2*max-1] we close them (in reverse order)
|
||||||
|
return ROOT;
|
||||||
|
}
|
||||||
|
// everything between [1..max-2] is a NEXT element tag (opening
|
||||||
|
// or closing)
|
||||||
|
return NEXT;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
if (n >= 2 * max) return -1;
|
||||||
|
if (open == 0) {
|
||||||
|
open = 1;
|
||||||
|
return '<';
|
||||||
|
}
|
||||||
|
if (open == 1 && n >= max) {
|
||||||
|
// we have opened the ROOT element + n-1 nested NEXT elements,
|
||||||
|
// so now we need to start closing them all in reverse order.
|
||||||
|
open = 2;
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
String tag = tag();
|
||||||
|
if (open > 0 && i < tag.length()) {
|
||||||
|
return tag.charAt(i++);
|
||||||
|
}
|
||||||
|
if (open > 0 && i == tag.length()) {
|
||||||
|
open = 0; i = 0; n++;
|
||||||
|
return '>';
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class TagFilter implements EventFilter {
|
||||||
|
int count;
|
||||||
|
final int max;
|
||||||
|
|
||||||
|
public TagFilter(int max) {
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filters everything except the ROOT element.
|
||||||
|
@Override
|
||||||
|
public boolean accept(XMLEvent event) {
|
||||||
|
int type = event.getEventType();
|
||||||
|
if (type == XMLEvent.START_ELEMENT) {
|
||||||
|
String loc = event.asStartElement().getName().getLocalPart();
|
||||||
|
if (count == 0 || count == 1) System.out.println("<" + loc + ">");
|
||||||
|
count++;
|
||||||
|
return ROOT.equals(loc);
|
||||||
|
}
|
||||||
|
if (type == XMLEvent.END_ELEMENT) {
|
||||||
|
if (count == max) System.out.println("Got " + count + " elements");
|
||||||
|
String loc = event.asEndElement().getName().getLocalPart();
|
||||||
|
count--;
|
||||||
|
if (count == 0 || count == 1) System.out.println("</" + loc + ">");
|
||||||
|
return ROOT.equals(loc);
|
||||||
|
}
|
||||||
|
if (type == XMLEvent.PROCESSING_INSTRUCTION) return true;
|
||||||
|
if (type == XMLEvent.START_DOCUMENT) return true;
|
||||||
|
if (type == XMLEvent.END_DOCUMENT) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user