JDK-8129963 : Working Code causes NPE after JVM optimization
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 8u45
  • Priority: P2
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2015-06-12
  • Updated: 2016-04-19
  • Resolved: 2016-04-19
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

FULL OS VERSION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
After updating to Java 8 the FileUpload-component in our JBoss Seam 2.1.2 application stopped working and started to throw a NPE. This always happened while the 5th file should be uploaded. Due to this investigation we assumed that this may be caused by some JVM code optimizations. We disabled any JVM code optimizations and that solved the issue.

We generalized the actual code found in org.jboss.seam.web.MultipartRequestImpl.parseRequest() and attached the resulting test case to this ticket.


THE PROBLEM WAS REPRODUCIBLE WITH -Xint FLAG: No

THE PROBLEM WAS REPRODUCIBLE WITH -server FLAG: Yes

REGRESSION.  Last worked in version 7u79

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
- start the attached test case using Java 8 and the -Xcomp JVM option

EXPECTED VERSUS ACTUAL BEHAVIOR :
- starting the test case using the -Xcomp JVM option should cause a NPE
- starting the test case using the -Xcomp AND -XX:CompileCommand=exclude,Tdb346Sut::checkSequence JVM options should NOT cause any exception
REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class Tdb346Sut {

  public static final String MULTIPART_REQUEST_BOUNDARY = "---------------------------7df17910508f6";

  public static final String MULTIPART_REQUEST_CONTENT = MULTIPART_REQUEST_BOUNDARY + System.lineSeparator() + "--"
      + MULTIPART_REQUEST_BOUNDARY + System.lineSeparator()
      + "Content-Disposition: form-data; name=\"javax.faces.ViewState\"" + System.lineSeparator()
      + System.lineSeparator() + "--" + MULTIPART_REQUEST_BOUNDARY + "--";

  private final byte[] multipartRequestBoundarySequenceBytes;

  private final InputStream multipartRequestContentStream;

  public Tdb346Sut(final Charset charset, String multipartRequestBoundary, final String multipartRequestContent) {
    super();

    this.multipartRequestBoundarySequenceBytes = multipartRequestBoundary.getBytes(charset);
    this.multipartRequestContentStream = new InputStream() {

      @Override
      public int read(byte[] b) throws IOException {
        return new ByteArrayInputStream(multipartRequestContent.getBytes(charset)).read(b);
      }

      @Override
      public int read() throws IOException {
        return 0;
      }
    };
  }

  public static void main(String[] args) throws IOException {
    System.out.println("NOTE that this requires Java 8 (jdk1.8.0_45)!");
    System.out.println("NOTE that this causes a NullPointerException when run with the -Xcomp JVM option!");
    System.out.println("NOTE that this works when run with the -Xcomp -XX:CompileCommand=exclude,"
        + Tdb346Sut.class.getName() + "::checkSequence JVM options!");

    // This may vary.
    int numberOfCodeExecutions = 100;

    new Tdb346Sut(StandardCharsets.UTF_8, MULTIPART_REQUEST_BOUNDARY, MULTIPART_REQUEST_CONTENT)
        .parseMultipartRequests(numberOfCodeExecutions);

    System.out.println("DONE!");
  }

  public int parseMultipartRequests(int numberOfCodeExecutions) throws IOException {
    for (int i = 0; i < numberOfCodeExecutions; i++) {
      new Tdb346Sut(StandardCharsets.UTF_8, Tdb346Sut.MULTIPART_REQUEST_BOUNDARY, Tdb346Sut.MULTIPART_REQUEST_CONTENT)
          .parseMultipartRequest();
    }

    return numberOfCodeExecutions;
  }

  /*
   * NOTE that the following implementation refers to org.jboss.seam.web.MultipartRequestImpl.parseRequest() found in
   * org.jboss.seam:jboss-seam:jar:2.1.2.
   */

  private void parseMultipartRequest() throws IOException {
    byte[] crLf = {0x0d, 0x0a};
    byte[] buffer = new byte[2048];

    ReadState readState = ReadState.BOUNDARY;

    int read = multipartRequestContentStream.read(buffer);
    int pos = 0;

    int loopCounter = 20;

    while (read > 0 && loopCounter > 0) {
      for (int i = 0; i < read; i++) {
        switch (readState) {
          case BOUNDARY : {
            if (checkSequence(buffer, i, multipartRequestBoundarySequenceBytes) && checkSequence(buffer, i + 2, crLf)) {
              readState = ReadState.HEADERS;
              i += 2;
              pos = i + 1;
            }

            break;
          }
          case HEADERS : {
            if (checkSequence(buffer, i, crLf)) {
              if (checkSequence(buffer, i + crLf.length, crLf)) {
                readState = ReadState.DATA;
                i += crLf.length;
                pos = i + 1;
              } else {
                pos = i + 1;
              }
            }

            break;
          }
          case DATA : {
            int chunkSize = 512;

            if (checkSequence(buffer, i - multipartRequestBoundarySequenceBytes.length - crLf.length, crLf)
                && checkSequence(buffer, i, multipartRequestBoundarySequenceBytes)) {
              if (checkSequence(buffer, i + crLf.length, crLf)) {
                i += crLf.length;
                pos = i + 1;
              } else {
                pos = i;
              }

              readState = ReadState.HEADERS;
            } else if (i > pos + multipartRequestBoundarySequenceBytes.length + chunkSize + crLf.length) {
              pos += chunkSize;
            }

            break;
          }
        }
      }

      if (pos < read) {
        int bytesNotRead = read - pos;

        System.arraycopy(buffer, pos, buffer, 0, bytesNotRead);

        read = multipartRequestContentStream.read(buffer, bytesNotRead, buffer.length - bytesNotRead);

        if (read == 0) {
          loopCounter--;
        }

        read += bytesNotRead;
      } else {
        read = multipartRequestContentStream.read(buffer);
      }

      pos = 0;
    }
  }

  private boolean checkSequence(byte[] data, int pos, byte[] seq) {
    if (pos - seq.length < -1 || pos >= data.length) {
      return false;
    }

    for (int i = 0; i < seq.length; i++) {
      if (data[pos - seq.length + i + 1] != seq[i]) {
        return false;
      }
    }

    return true;
  }

  private enum ReadState {
    BOUNDARY,
    HEADERS,
    DATA
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
- disabled JVM code optimizations by using the -Xint or -XX:CompileCommand JVM options


Comments
I'm able to reproduce this on Windows 2008 Server x64 with 8u40 and 8u60 until b17: $ /cygdrive/x/jdk/8u40/promoted/latest/binaries/windows-x64/bin/java -jar org.eclipse.jdt.core_3.11.0.v20150602-1242.jar -source 1.8 -target 1.8 Tdb346Sut.javaa $ /cygdrive/x/jdk/8u60/promoted/ea/b17/binaries/windows-x64/bin/java -Xcomp Tdb346Sut NOTE that this requires Java 8 (jdk1.8.0_45)! NOTE that this causes a NullPointerException when run with the -Xcomp JVM option! NOTE that this works when run with the -Xcomp -XX:CompileCommand=exclude,Tdb346Sut::checkSequence JVM options! Exception in thread "main" java.lang.NullPointerException at Tdb346Sut.checkSequence(Tdb346Sut.java:151) at Tdb346Sut.parseMultipartRequest(Tdb346Sut.java:106) at Tdb346Sut.parseMultipartRequests(Tdb346Sut.java:56) at Tdb346Sut.main(Tdb346Sut.java:48) The problem is fixed in 8u60 b18: $ /cygdrive/x/jdk/8u60/promoted/ea/b18/binaries/windows-x64/bin/java -Xcomp Tdb346Sut NOTE that this requires Java 8 (jdk1.8.0_45)! NOTE that this causes a NullPointerException when run with the -Xcomp JVM option! NOTE that this works when run with the -Xcomp -XX:CompileCommand=exclude,Tdb346Sut::checkSequence JVM options! DONE! I verified that this issue is a duplicate of JDK-8060036 which was fixed in 8u60 (JDK-8080660). Closed as duplicate.
19-04-2016

ILW = H(incorrect compiled code)M(common cases)M(disable compilation for affected code shapes) = P2
11-04-2016

The NPE seems to happen only with -Xcomp, but does not happen with "-Xcomp -XX:CompileCommand=exclude,Tdb346Sut::checkSequence". So it's likely to be a compiler bug.
30-06-2015

Tried to reproduce this issue with 7u79,8u45 and 8u60 ea but could not get null pointer exception.
26-06-2015