Skip to content

Commit 02c2603

Browse files
authored
QL: Merge pull request #119 from github/query-depends-on-tostring
Query: Query logic depends on `toString`
2 parents d6dd752 + 4d5901a commit 02c2603

File tree

1 file changed

+344
-0
lines changed

1 file changed

+344
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
/**
2+
* @name Using 'toString' in query logic
3+
* @description A query should not depend on the output of 'toString'.
4+
* @kind problem
5+
* @problem.severity error
6+
* @id ql/to-string-in-logic
7+
* @precision medium
8+
* @tags maintainability
9+
*/
10+
11+
import ql
12+
import codeql_ql.ast.internal.Builtins
13+
14+
class ToString extends Predicate {
15+
ToString() { this.getName() = "toString" }
16+
}
17+
18+
class ToStringCall extends Call {
19+
ToStringCall() { this.getTarget() instanceof ToString }
20+
}
21+
22+
class NodesPredicate extends Predicate {
23+
NodesPredicate() { this.getName() = "nodes" }
24+
}
25+
26+
class EdgesPredicate extends Predicate {
27+
EdgesPredicate() { this.getName() = "edges" }
28+
}
29+
30+
class RegexpReplaceAll extends BuiltinPredicate {
31+
RegexpReplaceAll() { this.getName() = "regexpReplaceAll" }
32+
}
33+
34+
class RegexpReplaceAllCall extends MemberCall {
35+
RegexpReplaceAllCall() { this.getTarget() instanceof RegexpReplaceAll }
36+
}
37+
38+
module DataFlow {
39+
private newtype TNode =
40+
TExprNode(Expr e) or
41+
TOutParameterNode(Parameter p) or
42+
TArgumentOutNode(VarAccess acc, Call call, int pos) {
43+
acc.(Argument).getCall() = call and acc.(Argument).getPosition() = pos
44+
} or
45+
TParameterNode(Parameter p)
46+
47+
/** An argument to a call. */
48+
class Argument extends Expr {
49+
Call call;
50+
int pos;
51+
52+
Argument() { call.getArgument(pos) = this }
53+
54+
/** Gets the call that has this argument. */
55+
Call getCall() { result = call }
56+
57+
/** Gets the position of this argument. */
58+
int getPosition() { result = pos }
59+
}
60+
61+
private newtype TParameter =
62+
TThisParameter(ClassPredicate p) or
63+
TResultParameter(Predicate p) { exists(p.getReturnType()) } or
64+
TVariableParameter(VarDecl v, int i, Predicate pred) { pred.getParameter(i) = v }
65+
66+
abstract class Parameter extends TParameter {
67+
string toString() { this.hasName(result) }
68+
69+
abstract predicate hasName(string name);
70+
71+
abstract int getIndex();
72+
73+
abstract Predicate getPredicate();
74+
}
75+
76+
class ThisParameter extends Parameter, TThisParameter {
77+
ClassPredicate p;
78+
79+
ThisParameter() { this = TThisParameter(p) }
80+
81+
override predicate hasName(string name) { name = "this" }
82+
83+
override int getIndex() { result = -1 }
84+
85+
override Predicate getPredicate() { result = p }
86+
}
87+
88+
class ResultParameter extends Parameter, TResultParameter {
89+
Predicate p;
90+
91+
ResultParameter() { this = TResultParameter(p) }
92+
93+
override predicate hasName(string name) { name = "result" }
94+
95+
override int getIndex() { result = -2 }
96+
97+
override Predicate getPredicate() { result = p }
98+
}
99+
100+
class VariableParameter extends Parameter, TVariableParameter {
101+
VarDecl v;
102+
int i;
103+
Predicate p;
104+
105+
VariableParameter() { this = TVariableParameter(v, i, p) }
106+
107+
override predicate hasName(string name) { name = v.getName() }
108+
109+
override int getIndex() { result = i }
110+
111+
override Predicate getPredicate() { result = p }
112+
}
113+
114+
class Node extends TNode {
115+
/** Gets the predicate to which this node belongs. */
116+
Predicate getPredicate() { none() } // overridden in subclasses
117+
118+
Expr asExpr() { result = this.(ExprNode).getExpr() }
119+
120+
/** Gets a textual representation of this element. */
121+
string toString() { none() } // overridden by subclasses
122+
}
123+
124+
/**
125+
* An expression, viewed as a node in a data flow graph.
126+
*/
127+
class ExprNode extends Node, TExprNode {
128+
Expr expr;
129+
130+
ExprNode() { this = TExprNode(expr) }
131+
132+
override string toString() { result = expr.toString() }
133+
134+
/** Gets the expression corresponding to this node. */
135+
Expr getExpr() { result = expr }
136+
}
137+
138+
class ParameterNode extends Node, TParameterNode {
139+
Parameter p;
140+
141+
ParameterNode() { this = TParameterNode(p) }
142+
143+
override string toString() { result = p.toString() }
144+
}
145+
146+
newtype TReturnKind =
147+
TNormalReturnKind() or
148+
TParameterOutKind(int i) { any(Parameter p).getIndex() = i }
149+
150+
/** A data flow node that represents the output of a call at the call site. */
151+
abstract class OutNode extends Node {
152+
/** Gets the underlying call. */
153+
abstract Call getCall();
154+
}
155+
156+
class ArgumentOutNode extends Node, TArgumentOutNode, OutNode {
157+
VarAccess acc;
158+
Call call;
159+
int pos;
160+
161+
ArgumentOutNode() { this = TArgumentOutNode(acc, call, pos) }
162+
163+
VarAccess getVarAccess() { result = acc }
164+
165+
override string toString() { result = acc.toString() + " [out]" }
166+
167+
override Call getCall() { result = call }
168+
169+
int getIndex() { result = pos }
170+
}
171+
172+
class OutParameterNode extends Node, TOutParameterNode {
173+
Parameter p;
174+
175+
OutParameterNode() { this = TOutParameterNode(p) }
176+
177+
Parameter getParameter() { result = p }
178+
179+
override string toString() { result = p.toString() }
180+
}
181+
182+
AstNode getParentOfExpr(Expr e) { result = e.getParent() }
183+
184+
Formula getEnclosing(Expr e) { result = getParentOfExpr+(e) }
185+
186+
Formula enlargeScopeStep(Formula f) { result.(Conjunction).getAnOperand() = f }
187+
188+
Formula enlargeScope(Formula f) {
189+
result = enlargeScopeStep*(f) and not exists(enlargeScopeStep(result))
190+
}
191+
192+
predicate varaccesValue(VarAccess va, VarDecl v, Formula scope) {
193+
va.getDeclaration() = v and
194+
scope = enlargeScope(getEnclosing(va))
195+
}
196+
197+
predicate thisValue(ThisAccess ta, Formula scope) { scope = enlargeScope(getEnclosing(ta)) }
198+
199+
predicate resultValue(ResultAccess ra, Formula scope) { scope = enlargeScope(getEnclosing(ra)) }
200+
201+
Formula getParentFormula(Formula f) { f.getParent() = result }
202+
203+
predicate valueStep(Expr e1, Expr e2) {
204+
exists(VarDecl v, Formula scope |
205+
varaccesValue(e1, v, scope) and
206+
varaccesValue(e2, v, scope)
207+
)
208+
or
209+
exists(VarDecl v, Formula f, Select sel |
210+
getParentFormula*(f) = sel.getWhere() and
211+
varaccesValue(e1, v, f) and
212+
sel.getExpr(_) = e2
213+
)
214+
or
215+
exists(Formula scope |
216+
thisValue(e1, scope) and
217+
thisValue(e2, scope)
218+
or
219+
resultValue(e1, scope) and
220+
resultValue(e2, scope)
221+
)
222+
or
223+
exists(InlineCast c |
224+
e1 = c and e2 = c.getBase()
225+
or
226+
e2 = c and e1 = c.getBase()
227+
)
228+
or
229+
exists(ComparisonFormula eq |
230+
eq.getSymbol() = "=" and
231+
eq.getAnOperand() = e1 and
232+
eq.getAnOperand() = e2 and
233+
e1 != e2
234+
)
235+
}
236+
237+
predicate paramStep(Expr e1, Parameter p2) {
238+
exists(VarDecl v |
239+
p2 = TVariableParameter(v, _, _) and
240+
varaccesValue(e1, v, _)
241+
)
242+
or
243+
exists(Formula scope |
244+
p2 = TThisParameter(scope.getEnclosingPredicate()) and
245+
thisValue(e1, scope)
246+
or
247+
p2 = TResultParameter(scope.getEnclosingPredicate()) and
248+
resultValue(e1, scope)
249+
)
250+
}
251+
252+
predicate additionalLocalStep(Node nodeFrom, Node nodeTo) {
253+
nodeFrom.asExpr().getType() instanceof StringClass and
254+
nodeFrom.asExpr().getType() instanceof StringClass and
255+
exists(BinOpExpr binop |
256+
nodeFrom.asExpr() = binop.getAnOperand() and
257+
nodeTo.asExpr() = binop
258+
)
259+
or
260+
nodeTo.asExpr().(RegexpReplaceAllCall).getBase() = nodeFrom.asExpr()
261+
}
262+
263+
predicate localStep(Node nodeFrom, Node nodeTo) {
264+
valueStep(nodeFrom.asExpr(), nodeTo.asExpr())
265+
or
266+
paramStep(nodeFrom.asExpr(), nodeTo.(OutParameterNode).getParameter())
267+
or
268+
valueStep(nodeFrom.(ArgumentOutNode).getVarAccess(), nodeTo.asExpr())
269+
or
270+
additionalLocalStep(nodeFrom, nodeTo)
271+
}
272+
273+
predicate step(Node nodeFrom, Node nodeTo) {
274+
// Local flow
275+
localStep(nodeFrom, nodeTo)
276+
or
277+
// Flow out of functions
278+
exists(Call call, Parameter p, OutParameterNode outParam, ArgumentOutNode outArg |
279+
outParam = nodeFrom and
280+
outArg = nodeTo
281+
|
282+
p = outParam.getParameter() and
283+
p.getPredicate() = call.getTarget() and
284+
outArg.getCall() = call and
285+
outArg.getIndex() = p.getIndex()
286+
)
287+
}
288+
289+
predicate flowsFromSource(Node node) {
290+
isSource(node.asExpr())
291+
or
292+
exists(Node mid | flowsFromSource(mid) | step(mid, node))
293+
}
294+
295+
predicate flowsToSink(Node node) {
296+
flowsFromSource(node) and isSink(node)
297+
or
298+
exists(Node mid | flowsToSink(mid) | step(node, mid))
299+
}
300+
301+
predicate isSink(Node sink) {
302+
sink.asExpr() = any(Select s).getExpr(_)
303+
or
304+
sink.getPredicate() instanceof NodesPredicate
305+
or
306+
sink.getPredicate() instanceof EdgesPredicate
307+
}
308+
309+
predicate isSource(ToStringCall toString) {
310+
not toString.getEnclosingPredicate() instanceof ToString and
311+
not toString.getEnclosingPredicate() instanceof NodesPredicate and
312+
not toString.getEnclosingPredicate() instanceof EdgesPredicate
313+
}
314+
}
315+
316+
predicate flowsToSelect(Expr e) {
317+
exists(DataFlow::Node source |
318+
source.asExpr() = e and
319+
DataFlow::flowsToSink(source)
320+
)
321+
}
322+
323+
from ToStringCall call
324+
where
325+
// It's not part of a toString call
326+
DataFlow::isSource(call) and
327+
// The call doesn't flow to a select
328+
not flowsToSelect(call) and
329+
// It's in a query
330+
call.getLocation().getFile().getBaseName().matches("%.ql") and
331+
// ... and not in a test
332+
not (
333+
call.getLocation()
334+
.getFile()
335+
.getAbsolutePath()
336+
.toLowerCase()
337+
.matches(["%test%", "%consistency%", "%meta%"])
338+
or
339+
call.getLocation()
340+
.getFile()
341+
.getAbsolutePath()
342+
.regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*")
343+
)
344+
select call, "Query logic depends on implementation of 'toString'."

0 commit comments

Comments
 (0)