Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8129963

Working Code causes NPE after JVM optimization

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Duplicate
    • Icon: P2 P2
    • None
    • 8u45
    • hotspot
    • x86_64
    • windows_7

      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

            thartmann Tobias Hartmann
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: