Summary: 1. One should use either a writer or a stream to send a response, but not both. 2. A response should be forwarded only if it was commited. Both properties are extracted from API comments on classes in the servlet API. Reviewed By: jvillard Differential Revision: D19514568 fbshipit-source-id: 79f0257edmaster
parent
6e6a33cd01
commit
fe736f4151
@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import javax.servlet.RequestDispatcher;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.ServletRequest;
|
||||||
|
import javax.servlet.ServletResponse;
|
||||||
|
|
||||||
|
class ServletTests {
|
||||||
|
void aBad(ServletResponse response) throws IOException {
|
||||||
|
PrintWriter w = response.getWriter();
|
||||||
|
ServletOutputStream s = response.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void bBad(ServletResponse response) throws IOException {
|
||||||
|
PrintWriter w = response.getWriter();
|
||||||
|
ServletOutputStream s = response.getOutputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
void cBad(ServletRequest request, ServletResponse response, RequestDispatcher dispatcher)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
PrintWriter w = response.getWriter();
|
||||||
|
dispatcher.forward(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A bugfix for cBad.
|
||||||
|
void cOk(ServletRequest request, ServletResponse response, RequestDispatcher dispatcher)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
PrintWriter w = response.getWriter();
|
||||||
|
w.flush();
|
||||||
|
dispatcher.forward(request, response);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,3 @@
|
|||||||
|
codetoanalyze/java/topl/servlet/ServletTests.java, ServletTests.aBad(javax.servlet.ServletResponse):void, 0, TOPL_ERROR, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/topl/servlet/ServletTests.java, ServletTests.bBad(javax.servlet.ServletResponse):void, 0, TOPL_ERROR, no_bucket, ERROR, []
|
||||||
|
codetoanalyze/java/topl/servlet/ServletTests.java, ServletTests.cBad(javax.servlet.ServletRequest,javax.servlet.ServletResponse,javax.servlet.RequestDispatcher):void, 0, TOPL_ERROR, no_bucket, ERROR, []
|
Binary file not shown.
@ -0,0 +1,25 @@
|
|||||||
|
// TODO: Checking any one of {InterleavedResponse, ForwardUncommited} is relatively fast. But,
|
||||||
|
// checking both is very slow.
|
||||||
|
|
||||||
|
// According to the Servlet API, one should use
|
||||||
|
// - a writer for a textual response
|
||||||
|
// - a stream for a binary response, or for a mixed binary+textual response
|
||||||
|
// but never both a writer and a stream.
|
||||||
|
property InterleavedResponse
|
||||||
|
message "A ServletResponse was asked for both a writer and a stream."
|
||||||
|
prefix "ServletResponse"
|
||||||
|
nondet (start)
|
||||||
|
start -> start: *
|
||||||
|
start -> gotWriter: getWriter(R)
|
||||||
|
start -> gotStream: getOutputStream(R)
|
||||||
|
gotWriter -> error: getOutputStream(r)
|
||||||
|
gotStream -> error: getWriter(r)
|
||||||
|
|
||||||
|
property ForwardUncommitted
|
||||||
|
message "A ServletResponse was forwarded before being committed."
|
||||||
|
nondet (start)
|
||||||
|
start -> start: *
|
||||||
|
start -> gotChannel: C = "ServletResponse.\\(getWriter\\|getOutputStream\\)"(R)
|
||||||
|
gotChannel -> ok: "\\(PrintWriter\\|ServletOutputStream\\).flush.*"(c)
|
||||||
|
gotChannel -> error: "RequestDispatcher.forward"(*, *, r)
|
||||||
|
|
@ -0,0 +1,19 @@
|
|||||||
|
// A weaker version of InterleavedResponse
|
||||||
|
property InterleavedResponseWeak
|
||||||
|
// vertex names: w = got writer; W = used writer; similarly for s, S
|
||||||
|
message "Incompatible methods for putting data into a response were used."
|
||||||
|
prefix "javax.servlet.ServletResponse"
|
||||||
|
nondet (start)
|
||||||
|
start -> start: *
|
||||||
|
start -> w: W = getWriter(R)
|
||||||
|
start -> s: S = getOutputStream(R)
|
||||||
|
w -> sw: S = getOutputStream(r)
|
||||||
|
s -> sw: W = getWriter(r)
|
||||||
|
w -> W: *(w)
|
||||||
|
sw -> sW: *(w)
|
||||||
|
s -> S: *(s)
|
||||||
|
sw -> Sw: *(s)
|
||||||
|
W -> sW: S = getOutputStream(r)
|
||||||
|
S -> Sw: W = getWriter(r)
|
||||||
|
sW -> error: *(s)
|
||||||
|
Sw -> error: *(w)
|
@ -1,9 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
||||||
*
|
|
||||||
* This source code is licensed under the MIT license found in the
|
|
||||||
* LICENSE file in the root directory of this source tree.
|
|
||||||
*/
|
|
||||||
class TomcatFail {
|
|
||||||
void f() {}
|
|
||||||
}
|
|
@ -1,52 +0,0 @@
|
|||||||
property ForwardUncommitted
|
|
||||||
message "A ServletResponse was forwarded before being committed."
|
|
||||||
// TODO assume InterleavedResponse_Weak
|
|
||||||
prefix "javax.servlet"
|
|
||||||
prefix "javax.servlet.{ServletOutputStream,ServletResponse}"
|
|
||||||
prefix "java.io.PrintWriter"
|
|
||||||
nondet (start)
|
|
||||||
start -> start: *
|
|
||||||
start -> tracking: R = "ServletResponse.\<init\>"
|
|
||||||
tracking -> ok: flushBuffer(r)
|
|
||||||
tracking -> gotWriter: W = getWriter(r)
|
|
||||||
gotWriter -> ok: flush(w)
|
|
||||||
gotWriter -> ok: flushBuffer(r)
|
|
||||||
tracking -> gotStream: S = getOutputStream(r)
|
|
||||||
gotStream -> ok: flush(s)
|
|
||||||
gotStream -> ok: flushBuffer(r)
|
|
||||||
tracking -> error: "RequestDispatcher.forward"(*, *, r)
|
|
||||||
gotWriter -> error: "RequestDispatcher.forward"(*, *, r)
|
|
||||||
gotStream -> error: "RequestDispatcher.forward"(*, *, r)
|
|
||||||
|
|
||||||
// The documentation of ServletResponse.getOutputStream says that "either this
|
|
||||||
// method getWriter may be called to write to the body, not both." So,
|
|
||||||
// technically, the property is InterleavedResponse1. However, this property is
|
|
||||||
// broken, which is why we also have the weaker version InterleavedResponse2.
|
|
||||||
property InterleavedResponse1
|
|
||||||
message "A ServletResponse was asked for both a writer and a stream."
|
|
||||||
prefix "javax.servlet.ServletResponse"
|
|
||||||
nondet (start)
|
|
||||||
start -> start: *
|
|
||||||
start -> gotWriter: W = getWriter(R)
|
|
||||||
start -> gotStream: S = getOutputStream(R)
|
|
||||||
gotWriter -> error: getOutputStream(r)
|
|
||||||
gotStream -> error: getWriter(r)
|
|
||||||
|
|
||||||
property InterleavedResponse2
|
|
||||||
// vertex names: w = got writer; W = used writer; similarly for s, S
|
|
||||||
message "Incompatible methods for putting data into a response were used."
|
|
||||||
prefix "javax.servlet.ServletResponse"
|
|
||||||
nondet (start)
|
|
||||||
start -> start: *
|
|
||||||
start -> w: W = getWriter(R)
|
|
||||||
start -> s: S = getOutputStream(R)
|
|
||||||
w -> sw: S = getOutputStream(r)
|
|
||||||
s -> sw: W = getWriter(r)
|
|
||||||
w -> W: *(w)
|
|
||||||
sw -> sW: *(w)
|
|
||||||
s -> S: *(s)
|
|
||||||
sw -> Sw: *(s)
|
|
||||||
W -> sW: S = getOutputStream(r)
|
|
||||||
S -> Sw: W = getWriter(r)
|
|
||||||
sW -> error: *(s)
|
|
||||||
Sw -> error: *(w)
|
|
Loading…
Reference in new issue