Skip to content

Commit ce70b86

Browse files
committed
Java: Add data-flow consistency checks.
1 parent dce121b commit ce70b86

File tree

3 files changed

+142
-1
lines changed

3 files changed

+142
-1
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* Provides consistency queries for checking invariants in the language-specific
3+
* data-flow classes and predicates.
4+
*/
5+
6+
private import DataFlowImplSpecific::Private
7+
private import DataFlowImplSpecific::Public
8+
private import TaintTrackingUtil
9+
10+
module Consistency {
11+
private predicate relevantNode(Node n) {
12+
n instanceof ArgumentNode or
13+
n instanceof ParameterNode or
14+
n instanceof ReturnNode or
15+
n = getAnOutNode(_, _) or
16+
simpleLocalFlowStep(n, _) or
17+
simpleLocalFlowStep(_, n) or
18+
jumpStep(n, _) or
19+
jumpStep(_, n) or
20+
storeStep(n, _, _) or
21+
storeStep(_, _, n) or
22+
readStep(n, _, _) or
23+
readStep(_, _, n) or
24+
defaultAdditionalTaintStep(n, _) or
25+
defaultAdditionalTaintStep(_, n)
26+
}
27+
28+
query predicate uniqueEnclosingCallable(Node n, string msg) {
29+
exists(int c |
30+
relevantNode(n) and
31+
c = count(n.getEnclosingCallable()) and
32+
c != 1 and
33+
if c > 1
34+
then msg = "Node does not have unique enclosing callable."
35+
else msg = "Node is missing an enclosing callable."
36+
)
37+
}
38+
39+
query predicate uniqueTypeBound(Node n, string msg) {
40+
exists(int c |
41+
relevantNode(n) and
42+
c = count(n.getTypeBound()) and
43+
c != 1 and
44+
if c > 1
45+
then msg = "Node does not have unique type bound."
46+
else msg = "Node is missing a type bound."
47+
)
48+
}
49+
50+
query predicate uniqueTypeRepr(Node n, string msg) {
51+
exists(int c |
52+
relevantNode(n) and
53+
c = count(getErasedRepr(n.getTypeBound())) and
54+
c != 1 and
55+
if c > 1
56+
then msg = "Node does not have unique type representation."
57+
else msg = "Node is missing a type representation."
58+
)
59+
}
60+
61+
query predicate parameterCallable(ParameterNode p, string msg) {
62+
exists(DataFlowCallable c | p.isParameterOf(c, _) and c != p.getEnclosingCallable()) and
63+
msg = "Callable mismatch for parameter."
64+
}
65+
66+
query predicate localFlowIsLocal(Node n1, Node n2, string msg) {
67+
simpleLocalFlowStep(n1, n2) and
68+
n1.getEnclosingCallable() != n2.getEnclosingCallable() and
69+
msg = "Local flow step does not preserve enclosing callable."
70+
}
71+
72+
private DataFlowType typeRepr() { result = getErasedRepr(any(Node n).getTypeBound()) }
73+
74+
query predicate compatibleTypesReflexive(DataFlowType t, string msg) {
75+
t = typeRepr() and
76+
not compatibleTypes(t, t) and
77+
msg = "Type compatibility predicate is not reflexive."
78+
}
79+
80+
query predicate unreachableNodeCCtx(Node n, DataFlowCall call, string msg) {
81+
isUnreachableInCall(n, call) and
82+
exists(DataFlowCallable c |
83+
c = n.getEnclosingCallable() and
84+
not viableCallable(call) = c
85+
) and
86+
msg = "Call context for isUnreachableInCall is inconsistent with call graph."
87+
}
88+
89+
query predicate localCallNodes(DataFlowCall call, Node n, string msg) {
90+
(
91+
n = getAnOutNode(call, _) and
92+
msg = "OutNode and call does not share enclosing callable."
93+
or
94+
n.(ArgumentNode).argumentOf(call, _) and
95+
msg = "ArgumentNode and call does not share enclosing callable."
96+
) and
97+
n.getEnclosingCallable() != call.getEnclosingCallable()
98+
}
99+
100+
query predicate postIsNotPre(PostUpdateNode n, string msg) {
101+
n.getPreUpdateNode() = n and msg = "PostUpdateNode should not equal its pre-update node."
102+
}
103+
104+
query predicate postIsInSameCallable(PostUpdateNode n, string msg) {
105+
n.getEnclosingCallable() != n.getPreUpdateNode().getEnclosingCallable() and
106+
msg = "PostUpdateNode does not share callable with its pre-update node."
107+
}
108+
109+
private predicate hasPost(Node n) { exists(PostUpdateNode post | post.getPreUpdateNode() = n) }
110+
111+
query predicate reverseRead(Node n, string msg) {
112+
exists(Node n2 | readStep(n, _, n2) and hasPost(n2) and not hasPost(n)) and
113+
msg = "Origin of readStep is missing a PostUpdateNode."
114+
}
115+
116+
query predicate storeIsPostUpdate(Node n, string msg) {
117+
storeStep(_, _, n) and
118+
not n instanceof PostUpdateNode and
119+
msg = "Store targets should be PostUpdateNodes."
120+
}
121+
122+
query predicate argHasPostUpdate(ArgumentNode n, string msg) {
123+
not hasPost(n) and
124+
not isImmutableOrUnobservable(n) and
125+
msg = "ArgumentNode is missing PostUpdateNode."
126+
}
127+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,14 @@ predicate isUnreachableInCall(Node n, DataFlowCall call) {
326326
}
327327

328328
int accessPathLimit() { result = 5 }
329+
330+
/**
331+
* Holds if `n` does not require a `PostUpdateNode` as it either cannot be
332+
* modified or its modification cannot be observed, for example if it is a
333+
* freshly created object that is not saved in a variable.
334+
*
335+
* This predicate is only used for consistency checks.
336+
*/
337+
predicate isImmutableOrUnobservable(Node n) {
338+
n.getType() instanceof ImmutableType or n instanceof ImplicitVarargsArray
339+
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import semmle.code.java.dataflow.InstanceAccess
1111

1212
cached
1313
private newtype TNode =
14-
TExprNode(Expr e) or
14+
TExprNode(Expr e) {
15+
not e.getType() instanceof VoidType and
16+
not e.getParent*() instanceof Annotation
17+
} or
1518
TExplicitParameterNode(Parameter p) { exists(p.getCallable().getBody()) } or
1619
TImplicitVarargsArray(Call c) {
1720
c.getCallee().isVarargs() and

0 commit comments

Comments
 (0)