Skip to content

Commit 2746f3f

Browse files
committed
Java: Add negative type check for uncaught exceptions.
1 parent fc1f9a7 commit 2746f3f

File tree

5 files changed

+77
-40
lines changed

5 files changed

+77
-40
lines changed

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowNodes.qll

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,24 @@ module Private {
536536

537537
/** Gets the catch clause associated with this node. */
538538
CatchClause getCatch() { this = TCatchTypeTestNode(result) }
539+
540+
Node getSuccessor(boolean match) {
541+
match = true and
542+
this.getCatch() = result.(CatchParameterNode).getCatch()
543+
or
544+
match = false and
545+
exists(TryStmt try, int i, CatchClause cc |
546+
cc = this.getCatch() and
547+
cc = try.getCatchClause(i) and
548+
// A catch-all does not allow for uncaught exceptions.
549+
not cc.getACaughtType() instanceof TypeThrowable
550+
|
551+
result.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1)
552+
or
553+
not exists(try.getCatchClause(i + 1)) and
554+
result.(UncaughtNode).getTry() = try
555+
)
556+
}
539557
}
540558

541559
/**

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowPrivate.qll

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,7 @@ module ExceptionFlow {
144144
)
145145
)
146146
or
147-
node1.(CatchTypeTestNode).getCatch() = node2.(CatchParameterNode).getCatch()
148-
or
149-
exists(TryStmt try, int i | node1.(CatchTypeTestNode).getCatch() = try.getCatchClause(i) |
150-
node2.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1)
151-
or
152-
not exists(try.getCatchClause(i + 1)) and
153-
node2.(UncaughtNode).getTry() = try
154-
)
147+
node1.(CatchTypeTestNode).getSuccessor(_) = node2
155148
}
156149
}
157150

@@ -243,7 +236,7 @@ predicate expectsContent(Node n, ContentSet c) {
243236
* possible flow. A single type is used for all numeric types to account for
244237
* numeric conversions, and otherwise the erasure is used.
245238
*/
246-
DataFlowType getErasedRepr(Type t) {
239+
RefType getErasedRepr0(Type t) {
247240
exists(Type e | e = t.getErasure() |
248241
if e instanceof NumericOrCharType
249242
then result.(BoxedType).getPrimitiveType().getName() = "double"
@@ -256,47 +249,59 @@ DataFlowType getErasedRepr(Type t) {
256249
t instanceof NullType and result instanceof TypeObject
257250
}
258251

252+
DataFlowType getErasedRepr(Type t) { result = TType(getErasedRepr0(t)) }
253+
254+
CatchClause getNegativeType(Node n) {
255+
exists(CatchTypeTestNode ct |
256+
n = ct.getSuccessor(false) and
257+
result = ct.getCatch()
258+
)
259+
}
260+
259261
pragma[noinline]
260262
DataFlowType getNodeType(Node n) {
263+
result.asNegType() = getNegativeType(n)
264+
or
265+
not exists(getNegativeType(n)) and
261266
result = getErasedRepr(n.getTypeBound())
262267
or
263268
result = FlowSummaryImpl::Private::summaryNodeType(n)
264269
}
265270

266271
/** Gets a string representation of a type returned by `getErasedRepr`. */
267-
string ppReprType(Type t) {
272+
private string ppErasedReprType(Type t) {
268273
if t.(BoxedType).getPrimitiveType().getName() = "double"
269274
then result = "Number"
270275
else result = t.toString()
271276
}
272277

273-
private predicate canContainBool(Type t) {
274-
t instanceof BooleanType or
275-
any(BooleanType b).(RefType).getASourceSupertype+() = t
276-
}
278+
/** Gets a string representation of a type returned by `getErasedRepr`. */
279+
string ppReprType(DataFlowType t) { result = t.toString() }
277280

278281
/**
279282
* Holds if `t1` and `t2` are compatible, that is, whether data can flow from
280283
* a node of type `t1` to a node of type `t2`.
281284
*/
282285
pragma[inline]
283-
predicate compatibleTypes(Type t1, Type t2) {
284-
exists(Type e1, Type e2 |
285-
e1 = getErasedRepr(t1) and
286-
e2 = getErasedRepr(t2)
287-
|
288-
// Because of `getErasedRepr`, `erasedHaveIntersection` is a sufficient
289-
// compatibility check, but `conContainBool` is kept as a dummy disjunct
290-
// to get the proper join-order.
291-
erasedHaveIntersection(e1, e2)
286+
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) {
287+
erasedHaveIntersection(t1.asType(), t2.asType())
288+
or
289+
exists(RefType pos, CatchClause neg |
290+
pos = t1.asType() and neg = t2.asNegType()
292291
or
293-
canContainBool(e1) and canContainBool(e2)
292+
pos = t2.asType() and neg = t1.asNegType()
293+
|
294+
not pos.getASourceSupertype*() = neg.getACaughtType()
294295
)
295296
}
296297

297298
/** A node that performs a type cast. */
298299
class CastNode extends Node {
299-
CastNode() { this.asExpr() instanceof CastingExpr or this instanceof CatchParameterNode }
300+
CastNode() {
301+
this.asExpr() instanceof CastingExpr or
302+
this instanceof CatchParameterNode or
303+
exists(getNegativeType(this))
304+
}
300305
}
301306

302307
private newtype TDataFlowCallable =
@@ -326,7 +331,20 @@ class DataFlowCallable extends TDataFlowCallable {
326331

327332
class DataFlowExpr = Expr;
328333

329-
class DataFlowType = RefType;
334+
private newtype TDataFlowType =
335+
TType(RefType t) { t = getErasedRepr0(_) } or
336+
TNegType(CatchClause cc)
337+
338+
class DataFlowType extends TDataFlowType {
339+
RefType asType() { this = TType(result) }
340+
341+
CatchClause asNegType() { this = TNegType(result) }
342+
343+
string toString() {
344+
result = ppErasedReprType(this.asType()) or
345+
result = "Not type: " + this.asNegType().getVariable().getTypeAccess()
346+
}
347+
}
330348

331349
private newtype TDataFlowCall =
332350
TCall(Call c) or
@@ -468,6 +486,7 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
468486
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
469487
receiver = call.(SummaryCall).getReceiver() and
470488
getNodeDataFlowType(receiver)
489+
.asType()
471490
.getSourceDeclaration()
472491
.(FunctionalInterface)
473492
.getRunMethod()

java/ql/lib/semmle/code/java/dataflow/internal/DataFlowUtil.qll

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ private newtype TContent =
204204
*/
205205
class Content extends TContent {
206206
/** Gets the type of the contained data for the purpose of type pruning. */
207-
abstract DataFlowType getType();
207+
abstract Type getType();
208208

209209
/** Gets a textual representation of this element. */
210210
abstract string toString();
@@ -229,7 +229,7 @@ class FieldContent extends Content, TFieldContent {
229229

230230
InstanceField getField() { result = f }
231231

232-
override DataFlowType getType() { result = getErasedRepr(f.getType()) }
232+
override Type getType() { result = getErasedRepr0(f.getType()) }
233233

234234
override string toString() { result = f.toString() }
235235

@@ -240,28 +240,28 @@ class FieldContent extends Content, TFieldContent {
240240

241241
/** A reference through an array. */
242242
class ArrayContent extends Content, TArrayContent {
243-
override DataFlowType getType() { result instanceof TypeObject }
243+
override Type getType() { result instanceof TypeObject }
244244

245245
override string toString() { result = "[]" }
246246
}
247247

248248
/** A reference through the contents of some collection-like container. */
249249
class CollectionContent extends Content, TCollectionContent {
250-
override DataFlowType getType() { result instanceof TypeObject }
250+
override Type getType() { result instanceof TypeObject }
251251

252252
override string toString() { result = "<element>" }
253253
}
254254

255255
/** A reference through a map key. */
256256
class MapKeyContent extends Content, TMapKeyContent {
257-
override DataFlowType getType() { result instanceof TypeObject }
257+
override Type getType() { result instanceof TypeObject }
258258

259259
override string toString() { result = "<map.key>" }
260260
}
261261

262262
/** A reference through a map value. */
263263
class MapValueContent extends Content, TMapValueContent {
264-
override DataFlowType getType() { result instanceof TypeObject }
264+
override Type getType() { result instanceof TypeObject }
265265

266266
override string toString() { result = "<map.value>" }
267267
}
@@ -274,7 +274,7 @@ class SyntheticFieldContent extends Content, TSyntheticFieldContent {
274274

275275
SyntheticField getField() { result = s }
276276

277-
override DataFlowType getType() { result = getErasedRepr(s.getType()) }
277+
override Type getType() { result = getErasedRepr0(s.getType()) }
278278

279279
override string toString() { result = s.toString() }
280280
}

java/ql/lib/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSum
2626
SummaryCall summaryDataFlowCall(Node receiver) { result.getReceiver() = receiver }
2727

2828
/** Gets the type of content `c`. */
29-
DataFlowType getContentType(Content c) { result = c.getType() }
29+
DataFlowType getContentType(Content c) { result.asType() = c.getType() }
3030

3131
/** Gets the return type of kind `rk` for callable `c`. */
3232
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
@@ -35,17 +35,17 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
3535
or
3636
rk instanceof ExceptionReturnKind and
3737
exists(c) and
38-
result instanceof TypeThrowable
38+
result.asType() instanceof TypeThrowable
3939
}
4040

4141
/**
4242
* Gets the type of the `i`th parameter in a synthesized call that targets a
4343
* callback of type `t`.
4444
*/
4545
DataFlowType getCallbackParameterType(DataFlowType t, int i) {
46-
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getParameterType(i))
46+
result = getErasedRepr(t.asType().(FunctionalInterface).getRunMethod().getParameterType(i))
4747
or
48-
result = getErasedRepr(t.(FunctionalInterface)) and i = -1
48+
result = getErasedRepr(t.asType().(FunctionalInterface)) and i = -1
4949
}
5050

5151
/**
@@ -54,11 +54,11 @@ DataFlowType getCallbackParameterType(DataFlowType t, int i) {
5454
*/
5555
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
5656
rk instanceof NormalReturnKind and
57-
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getReturnType())
57+
result = getErasedRepr(t.asType().(FunctionalInterface).getRunMethod().getReturnType())
5858
or
5959
rk instanceof ExceptionReturnKind and
6060
exists(t) and
61-
result instanceof TypeThrowable
61+
result.asType() instanceof TypeThrowable
6262
}
6363

6464
/** Gets the type of synthetic global `sg`. */

java/ql/test/library-tests/dataflow/exceptions/A.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ void foo(int i) {
4747
} catch (MyOtherException e) {
4848
sink(e.msg); // $ hasValueFlow=other
4949
} catch (Exception e) {
50-
sink(((MyOtherException)e).msg); // $ SPURIOUS: hasValueFlow=other
50+
sink(((MyOtherException)e).msg); // no flow
5151
}
5252
}
5353

0 commit comments

Comments
 (0)