https://issues.apache.org/bugzilla/show_bug.cgi?id=57341

            Bug ID: 57341
           Summary: JUnitReport task causes
                    OutOfMemoryError/StackOverflowError at
                    junit_frames.br$dash$replace()
           Product: Ant
           Version: 1.9.4
          Hardware: PC
            Status: NEW
          Severity: critical
          Priority: P2
         Component: AntUnit
          Assignee: notifications@ant.apache.org
          Reporter: ryan.benn...@the-logic-group.com

While performing a JUnitReport task, our build server started consistently
reporting Stack Overflow errors:

build.xml:773: The following error occurred while executing this line:
build.xml:923: java.lang.StackOverflowError
    at
com.sun.org.apache.xml.internal.serializer.ToStream.processDirty(ToStream.java:1571)
    at
com.sun.org.apache.xml.internal.serializer.ToStream.characters(ToStream.java:1489)
    at
com.sun.org.apache.xml.internal.serializer.ToHTMLStream.characters(ToHTMLStream.java:1529)
    at
com.sun.org.apache.xml.internal.serializer.ToStream.characters(ToStream.java:1614)
    at
com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet.characters(AbstractTranslet.java:621)
    at junit_frames.br$dash$replace()
    at junit_frames.br$dash$replace()
    at junit_frames.br$dash$replace()
    ...
        at junit_frames.br$dash$replace()
    x1000

I tested locally and got a slightly different result:

build.xml:923: java.lang.OutOfMemoryError: Java heap space
        at java.util.Arrays.copyOfRange(Arrays.java:2694)
        at java.lang.String.<init>(String.java:203)
        at java.lang.String.substring(String.java:1877)
        at
com.sun.org.apache.xalan.internal.xsltc.runtime.BasisLibrary.substring_afterF(BasisLibrary.java:329)
        at junit_frames.br$dash$replace()
        at junit_frames.br$dash$replace()
        at junit_frames.br$dash$replace()
    ...
        at junit_frames.br$dash$replace()
    x1000

Monitoring the build on my workstation, Java Mission Control showed the memory
spiking to over 1.47GB before I got the out of memory error.

I had a look at the br-replace template in the $ANT_HOME/etc/junit-frames.xsl
and $ANT_HOME/etc/junit-noframes.xsl files and it is performing a recursive
replace of line returns one by one, so as soon as you perform a br-replace on a
file with more line returns than your stack limit, you're going to get this
error, unless you run out of memory first.

I managed to reimplement the br-replace template to be less stack/heap
intensive. After trying unsuccessfully to use the java
String.replace/replaceAll and StringUtils.replace functions that are used
elsewhere (which seem to have issues, possibly JVM dependent) I went for a
plain XSLT 1.0 implementation using a binary-subdivision approach that splits
the string approximately evenly on the nearest line break on large strings:

<xsl:template name="br-replace">
  <xsl:param name="word"/>
  <xsl:param name="splitlimit">32</xsl:param>
  <xsl:variable name="secondhalflen"
select="(string-length($word)+(string-length($word) mod 2)) div 2"/>
  <xsl:variable name="secondhalfword" select="substring($word,
$secondhalflen)"/>
  <!-- When word is very big, a recursive replace is very heap/stack expensive,
so subdivide on line break after middle of string -->
  <xsl:choose>
    <xsl:when test="(string-length($word) > $splitlimit) and
(contains($secondhalfword, '&#xa;'))">
      <xsl:variable name="secondhalfend"
select="substring-after($secondhalfword, '&#xa;')"/>
      <xsl:variable name="firsthalflen" select="string-length($word) -
$secondhalflen"/>
      <xsl:variable name="firsthalfword" select="substring($word, 1,
$firsthalflen)"/>
      <xsl:variable name="firsthalfend"
select="substring-before($secondhalfword, '&#xa;')"/>
      <xsl:call-template name="br-replace">
        <xsl:with-param name="word"
select="concat($firsthalfword,$firsthalfend)"/>
      </xsl:call-template>
      <br/>
      <xsl:call-template name="br-replace">
        <xsl:with-param name="word" select="$secondhalfend"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:when test="contains($word, '&#xa;')">
      <xsl:value-of select="substring-before($word, '&#xa;')"/>
      <br/>
      <xsl:call-template name="br-replace">
        <xsl:with-param name="word" select="substring-after($word, '&#xa;')"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
  <xsl:value-of select="$word"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This implementation is much more heap/stack friendly. JMC only reported a peak
of 621MB, compared to the 1.47GB it hit before overflowing.

-- 
You are receiving this mail because:
You are the assignee for the bug.

Reply via email to