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