Skip to content

Commit da1418b

Browse files
committed
Java: Add negative type check for uncaught exceptions.
1 parent 50b9339 commit da1418b

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
@@ -514,6 +514,24 @@ module Private {
514514

515515
/** Gets the catch clause associated with this node. */
516516
CatchClause getCatch() { this = TCatchTypeTestNode(result) }
517+
518+
Node getSuccessor(boolean match) {
519+
match = true and
520+
this.getCatch() = result.(CatchParameterNode).getCatch()
521+
or
522+
match = false and
523+
exists(TryStmt try, int i, CatchClause cc |
524+
cc = this.getCatch() and
525+
cc = try.getCatchClause(i) and
526+
// A catch-all does not allow for uncaught exceptions.
527+
not cc.getACaughtType() instanceof TypeThrowable
528+
|
529+
result.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1)
530+
or
531+
not exists(try.getCatchClause(i + 1)) and
532+
result.(UncaughtNode).getTry() = try
533+
)
534+
}
517535
}
518536

519537
/**

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

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -142,14 +142,7 @@ module ExceptionFlow {
142142
)
143143
)
144144
or
145-
node1.(CatchTypeTestNode).getCatch() = node2.(CatchParameterNode).getCatch()
146-
or
147-
exists(TryStmt try, int i | node1.(CatchTypeTestNode).getCatch() = try.getCatchClause(i) |
148-
node2.(CatchTypeTestNode).getCatch() = try.getCatchClause(i + 1)
149-
or
150-
not exists(try.getCatchClause(i + 1)) and
151-
node2.(UncaughtNode).getTry() = try
152-
)
145+
node1.(CatchTypeTestNode).getSuccessor(_) = node2
153146
}
154147
}
155148

@@ -241,7 +234,7 @@ predicate expectsContent(Node n, ContentSet c) {
241234
* possible flow. A single type is used for all numeric types to account for
242235
* numeric conversions, and otherwise the erasure is used.
243236
*/
244-
DataFlowType getErasedRepr(Type t) {
237+
RefType getErasedRepr0(Type t) {
245238
exists(Type e | e = t.getErasure() |
246239
if e instanceof NumericOrCharType
247240
then result.(BoxedType).getPrimitiveType().getName() = "double"
@@ -254,47 +247,59 @@ DataFlowType getErasedRepr(Type t) {
254247
t instanceof NullType and result instanceof TypeObject
255248
}
256249

250+
DataFlowType getErasedRepr(Type t) { result = TType(getErasedRepr0(t)) }
251+
252+
CatchClause getNegativeType(Node n) {
253+
exists(CatchTypeTestNode ct |
254+
n = ct.getSuccessor(false) and
255+
result = ct.getCatch()
256+
)
257+
}
258+
257259
pragma[noinline]
258260
DataFlowType getNodeType(Node n) {
261+
result.asNegType() = getNegativeType(n)
262+
or
263+
not exists(getNegativeType(n)) and
259264
result = getErasedRepr(n.getTypeBound())
260265
or
261266
result = FlowSummaryImpl::Private::summaryNodeType(n)
262267
}
263268

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

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

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

295296
/** A node that performs a type cast. */
296297
class CastNode extends Node {
297-
CastNode() { this.asExpr() instanceof CastingExpr or this instanceof CatchParameterNode }
298+
CastNode() {
299+
this.asExpr() instanceof CastingExpr or
300+
this instanceof CatchParameterNode or
301+
exists(getNegativeType(this))
302+
}
298303
}
299304

300305
private newtype TDataFlowCallable =
@@ -324,7 +329,20 @@ class DataFlowCallable extends TDataFlowCallable {
324329

325330
class DataFlowExpr = Expr;
326331

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

329347
private newtype TDataFlowCall =
330348
TCall(Call c) or
@@ -467,6 +485,7 @@ predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c)
467485
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) {
468486
receiver = call.(SummaryCall).getReceiver() and
469487
getNodeDataFlowType(receiver)
488+
.asType()
470489
.getSourceDeclaration()
471490
.(FunctionalInterface)
472491
.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
@@ -28,7 +28,7 @@ Node summaryNode(SummarizedCallable c, SummaryNodeState state) { result = getSum
2828
SummaryCall summaryDataFlowCall(Node receiver) { result.getReceiver() = receiver }
2929

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

3333
/** Gets the return type of kind `rk` for callable `c`. */
3434
DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
@@ -37,17 +37,17 @@ DataFlowType getReturnType(SummarizedCallable c, ReturnKind rk) {
3737
or
3838
rk instanceof ExceptionReturnKind and
3939
exists(c) and
40-
result instanceof TypeThrowable
40+
result.asType() instanceof TypeThrowable
4141
}
4242

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

5353
/**
@@ -56,11 +56,11 @@ DataFlowType getCallbackParameterType(DataFlowType t, int i) {
5656
*/
5757
DataFlowType getCallbackReturnType(DataFlowType t, ReturnKind rk) {
5858
rk instanceof NormalReturnKind and
59-
result = getErasedRepr(t.(FunctionalInterface).getRunMethod().getReturnType())
59+
result = getErasedRepr(t.asType().(FunctionalInterface).getRunMethod().getReturnType())
6060
or
6161
rk instanceof ExceptionReturnKind and
6262
exists(t) and
63-
result instanceof TypeThrowable
63+
result.asType() instanceof TypeThrowable
6464
}
6565

6666
bindingset[provenance]

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)