|
| 1 | +/** |
| 2 | + * Finds code which calls a stream reading method, such as `InputStream#read()`, |
| 3 | + * casts the result to a smaller numeric type such as `byte` and afterwards |
| 4 | + * checks for end-of-file by comparing the value against `-1`. |
| 5 | + * |
| 6 | + * This can lead to an erroneously detected end-of-file because due to the cast |
| 7 | + * the max unsigned value of the target type, e.g. the byte value 255, becomes |
| 8 | + * -1 as well. Instead the code should first check for -1 and only afterwards |
| 9 | + * perform the cast. |
| 10 | + * |
| 11 | + * For example: |
| 12 | + * ```java |
| 13 | + * // BAD |
| 14 | + * byte b = (byte) in.read(); |
| 15 | + * if (b == -1) ... |
| 16 | + * |
| 17 | + * // GOOD |
| 18 | + * int read = in.read(); |
| 19 | + * if (read == -1) ... |
| 20 | + * byte b = (byte) read; |
| 21 | + * ``` |
| 22 | + * |
| 23 | + * This query was inspired by <https://github.com/openjdk/jdk/pull/30357>. |
| 24 | + * |
| 25 | + * @kind problem |
| 26 | + * @id TODO |
| 27 | + */ |
| 28 | + |
| 29 | +import java |
| 30 | +import semmle.code.java.dataflow.DataFlow |
| 31 | + |
| 32 | +class CastTargetType extends Type { |
| 33 | + CastTargetType() { |
| 34 | + this.hasName(["byte", "char", "short"]) or |
| 35 | + this.(Class).hasQualifiedName("java.lang", ["Byte", "Character", "Short"]) |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +// Checks for flow from a method returning an `int`, to conversion to `byte` and comparison with `-1` |
| 40 | +from MethodCall readCall, EqualityTest eofCheck, Expr castedExpr |
| 41 | +where |
| 42 | + readCall.getType().hasName("int") and |
| 43 | + DataFlow::localExprFlow(readCall, castedExpr) and |
| 44 | + // Assumes that dataflow implicitly continues through cast to `byte` |
| 45 | + castedExpr.getType() instanceof CastTargetType and |
| 46 | + eofCheck.getAnOperand() = castedExpr and |
| 47 | + // Don't check for CompileTimeConstantExpr because that would also include values such as `0xFF` or constants which do not represent EOF |
| 48 | + eofCheck.getAnOperand().(MinusExpr).getExpr().(IntegerLiteral).getIntValue() = 1 |
| 49 | +select eofCheck, "Incorrect end-of-file check after cast" |
0 commit comments