Skip to content

Commit bac573b

Browse files
committed
QL4QL: Add DependencyPath.ql query
1 parent 28b7ab7 commit bac573b

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/**
2+
* Defines a graph for reasoning about dependencies between different predicates,
3+
* that is, whether one would force evaluation of another (unless magic).
4+
*/
5+
6+
private import codeql_ql.ast.Ast
7+
8+
newtype TPathNode =
9+
MkAstNode(AstNode node) or
10+
MkTypeNode(Type type) { not type instanceof DontCareType }
11+
12+
class PathNode extends TPathNode {
13+
AstNode asAstNode() { this = MkAstNode(result) }
14+
15+
Type asType() { this = MkTypeNode(result) }
16+
17+
private string getOverrideToString() {
18+
exists(Predicate p | this.asAstNode() = p |
19+
result = p.(ClassPredicate).getDeclaringType().getName() + "." + p.getName()
20+
or
21+
result = p.(ClasslessPredicate).getName()
22+
)
23+
or
24+
result = this.asAstNode().(TopLevel).getFile().getRelativePath()
25+
}
26+
27+
string toString() {
28+
result = this.getOverrideToString()
29+
or
30+
not exists(this.getOverrideToString()) and
31+
(
32+
result = this.asAstNode().toString()
33+
or
34+
result = this.asType().toString()
35+
)
36+
}
37+
38+
/**
39+
* Tries to provide a more helpful location, if possible.
40+
*/
41+
private Location getOverrideLocation() {
42+
result = this.asType().(ClassCharType).getDeclaration().getCharPred().getLocation()
43+
}
44+
45+
predicate hasLocationInfo(
46+
string filepath, int startline, int startcolumn, int endline, int endcolumn
47+
) {
48+
this.getOverrideLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
49+
or
50+
not exists(this.getOverrideLocation()) and
51+
(
52+
this.asType().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
53+
or
54+
this.asAstNode().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
55+
)
56+
}
57+
}
58+
59+
private Predicate getAnOverriddenPredicate(Predicate p) { predOverrides(p, result) }
60+
61+
private predicate isRootDef(Predicate p) { not exists(getAnOverriddenPredicate(p)) }
62+
63+
private Predicate getARootDef(Predicate p) {
64+
result = getAnOverriddenPredicate*(p) and
65+
isRootDef(result)
66+
}
67+
68+
private TypeExpr getABaseType(Class cls, boolean abstractExtension) {
69+
result = cls.getASuperType() and
70+
if result.getResolvedType().(ClassType).getDeclaration().isAbstract()
71+
then abstractExtension = true
72+
else abstractExtension = false
73+
or
74+
result = cls.getAnInstanceofType() and
75+
abstractExtension = false
76+
}
77+
78+
pragma[nomagic]
79+
predicate rawEdge(PathNode pred, PathNode succ) {
80+
exists(Predicate predicat | pred.asAstNode() = predicat |
81+
succ.asAstNode().getEnclosingPredicate() = predicat
82+
or
83+
getAnOverriddenPredicate(succ.asAstNode()) = predicat
84+
or
85+
succ.asType() = predicat.(ClassPredicate).getDeclaringType()
86+
)
87+
or
88+
exists(Call call |
89+
pred.asAstNode() = call and
90+
succ.asAstNode() = getARootDef(call.getTarget())
91+
)
92+
or
93+
exists(TypeExpr t | pred.asAstNode() = t |
94+
if t = getABaseType(_, true)
95+
then
96+
// When extending an abstract class, the typename in the 'extends' clause is considered to target
97+
// only the ClassCharType. The ClassType represents the union of concrete subtypes.
98+
succ.asType().(ClassCharType).getClassType() = t.getResolvedType()
99+
else succ.asType() = t.getResolvedType()
100+
)
101+
or
102+
exists(ClassType classType | pred.asType() = classType |
103+
// Always add an edge from ClassType to ClassCharType. For abstract class this makes the path shorter
104+
// and more obvious than going through an arbitrary subclass.
105+
succ.asType().(ClassCharType).getClassType() = classType
106+
or
107+
classType.getDeclaration().isAbstract() and
108+
succ.asType().(ClassType).getDeclaration().getASuperType().getResolvedType() = classType
109+
)
110+
or
111+
exists(ClassCharType charType, Class cls |
112+
pred.asType() = charType and
113+
cls = charType.getClassType().getDeclaration()
114+
|
115+
succ.asAstNode() = cls.getCharPred()
116+
or
117+
succ.asAstNode() = cls.getAField().getVarDecl().getTypeExpr()
118+
or
119+
succ.asAstNode() = getABaseType(cls, _)
120+
)
121+
or
122+
exists(UnionType t |
123+
pred.asType() = t and
124+
succ.asAstNode() = t.getDeclaration().getUnionMember()
125+
)
126+
or
127+
exists(NewTypeType t |
128+
pred.asType() = t and
129+
succ.asType() = t.getABranch()
130+
)
131+
or
132+
exists(NewTypeBranchType t |
133+
pred.asType() = t and
134+
succ.asAstNode() = t.getDeclaration() // map to the Predicate AST node
135+
)
136+
or
137+
exists(TopLevel top |
138+
pred.asAstNode() = top and
139+
succ.asAstNode().getFile() = top.getFile()
140+
)
141+
}
142+
143+
private PathNode getAPredecessor(PathNode node) { rawEdge(result, node) }
144+
145+
private PathNode getASuccessor(PathNode node) { rawEdge(node, result) }
146+
147+
signature module DependencyConfig {
148+
/**
149+
* Holds if we should explore the transitive dependencies of `source`.
150+
*/
151+
predicate isSource(PathNode source);
152+
153+
/**
154+
* Holds if a transitive dependency from a source to `sink` should be reported.
155+
*/
156+
predicate isSink(PathNode sink);
157+
}
158+
159+
module PathGraph<DependencyConfig C> {
160+
private PathNode reachableFromSource() {
161+
C::isSource(result)
162+
or
163+
result = getASuccessor(reachableFromSource())
164+
}
165+
166+
private PathNode reachableSink() {
167+
C::isSink(result) and
168+
result = reachableFromSource()
169+
}
170+
171+
private PathNode relevantNode() {
172+
result = reachableSink()
173+
or
174+
result = getAPredecessor(relevantNode()) and
175+
result = reachableFromSource()
176+
}
177+
178+
query predicate edges(PathNode pred, PathNode succ) {
179+
pred = relevantNode() and
180+
succ = relevantNode() and
181+
rawEdge(pred, succ)
182+
}
183+
184+
query predicate nodes(PathNode node) { node = relevantNode() }
185+
186+
predicate hasFlowPath(PathNode source, PathNode sink) {
187+
C::isSource(source) and
188+
C::isSink(sink) and
189+
edges+(source, sink)
190+
}
191+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @name Dependency path
3+
* @kind path-problem
4+
* @description Reports known dependency paths between different predicates or classes.
5+
* @precision medium
6+
* @severity info
7+
* @tags exploration
8+
* @id ql/explore/dependency-path
9+
*/
10+
11+
//
12+
// This query is for exploration purposes can be edited by hand according to your use-case.
13+
// Note that dependencies introduced by 'magic' are not recognized by this query.
14+
//
15+
import codeql_ql.ast.Ast
16+
import codeql_ql.dependency.DependencyPath
17+
18+
module Config implements DependencyConfig {
19+
/**
20+
* Holds if we should explore the transitive dependencies of `source`.
21+
*/
22+
predicate isSource(PathNode source) {
23+
// A top-level is considered to depend on everything in the file, which can be useful for generating
24+
// quick overview of which files depend on a given sink.
25+
source.asAstNode() instanceof TopLevel
26+
}
27+
28+
/**
29+
* Holds if a transitive dependency from a source to `sink` should be reported.
30+
*/
31+
predicate isSink(PathNode sink) { sink.asAstNode().(Predicate).getName() = "isLocalSourceNode" }
32+
}
33+
34+
import PathGraph<Config>
35+
36+
from PathNode source, PathNode sink
37+
where hasFlowPath(source, sink)
38+
select source, source, sink, "$@ depends on $@.", source, source.toString(), sink, sink.toString()

0 commit comments

Comments
 (0)