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