Skip to content

Commit 8502939

Browse files
authored
Merge pull request github#11081 from asgerf/ql/dependency-paths
QL4QL: Add DependencyPath.ql query
2 parents 01f3150 + fbcdb53 commit 8502939

File tree

2 files changed

+285
-0
lines changed

2 files changed

+285
-0
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
private predicate basicEdge(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+
or
142+
// Add bidirectional edges between a class declaration and its type.
143+
exists(Class cls |
144+
pred.asAstNode() = cls and succ.asType() = cls.getType()
145+
or
146+
succ.asAstNode() = cls and pred.asType() = cls.getType()
147+
)
148+
}
149+
150+
private predicate cacheEdge(PathNode pred, PathNode succ) {
151+
// At a cached module, add bidirectional edges to every cached member
152+
exists(Module mod, Declaration decl |
153+
mod.hasAnnotation("cached") and
154+
decl = mod.getAChild() and
155+
decl.hasAnnotation("cached")
156+
|
157+
pred.asAstNode() = mod and succ.asAstNode() = decl
158+
or
159+
succ.asAstNode() = mod and pred.asAstNode() = decl
160+
)
161+
or
162+
// At a cached class, add edges from the class to its cached member predicates
163+
exists(Class cls, Predicate member |
164+
cls.hasAnnotation("cached") and
165+
member = cls.getAClassPredicate() and
166+
member.hasAnnotation("cached") and
167+
pred.asAstNode() = cls and
168+
succ.asAstNode() = member
169+
)
170+
}
171+
172+
signature module DependencyConfig {
173+
/**
174+
* Holds if we should explore the transitive dependencies of `source`.
175+
*/
176+
predicate isSource(PathNode source);
177+
178+
/**
179+
* Holds if a transitive dependency from a source to `sink` should be reported.
180+
*/
181+
predicate isSink(PathNode sink);
182+
183+
/**
184+
* Holds if the `cached` members of a `cached` module or class should be unified.
185+
*
186+
* Whether to set this depends on your use-case:
187+
* - If you wish to know why one predicate causes another predicate to be evaluated, this should be `any()`.
188+
* - If you wish to investigate recursion patterns or understand why the value of one predicate
189+
* is influenced by another predicate, it should be `none()`.
190+
*/
191+
predicate followCacheDependencies();
192+
}
193+
194+
module PathGraph<DependencyConfig C> {
195+
private predicate rawEdge(PathNode pred, PathNode succ) {
196+
basicEdge(pred, succ)
197+
or
198+
C::followCacheDependencies() and
199+
cacheEdge(pred, succ)
200+
}
201+
202+
private PathNode getAPredecessor(PathNode node) { rawEdge(result, node) }
203+
204+
private PathNode getASuccessor(PathNode node) { rawEdge(node, result) }
205+
206+
private PathNode reachableFromSource() {
207+
C::isSource(result)
208+
or
209+
result = getASuccessor(reachableFromSource())
210+
}
211+
212+
private PathNode reachableSink() {
213+
C::isSink(result) and
214+
result = reachableFromSource()
215+
}
216+
217+
private PathNode relevantNode() {
218+
result = reachableSink()
219+
or
220+
result = getAPredecessor(relevantNode()) and
221+
result = reachableFromSource()
222+
}
223+
224+
query predicate edges(PathNode pred, PathNode succ) {
225+
pred = relevantNode() and
226+
succ = relevantNode() and
227+
rawEdge(pred, succ)
228+
}
229+
230+
query predicate nodes(PathNode node) { node = relevantNode() }
231+
232+
predicate hasFlowPath(PathNode source, PathNode sink) {
233+
C::isSource(source) and
234+
C::isSink(sink) and
235+
edges+(source, sink)
236+
}
237+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
* Holds if the `cached` members of a `cached` module or class should be unified.
35+
*
36+
* Whether to set this depends on your use-case:
37+
* - If you wish to know why one predicate causes another predicate to be evaluated, this should be `any()`.
38+
* - If you wish to investigate recursion patterns or understand why the value of one predicate
39+
* is influenced by another predicate, it should be `none()`.
40+
*/
41+
predicate followCacheDependencies() { any() }
42+
}
43+
44+
import PathGraph<Config>
45+
46+
from PathNode source, PathNode sink
47+
where hasFlowPath(source, sink)
48+
select source, source, sink, "$@ depends on $@.", source, source.toString(), sink, sink.toString()

0 commit comments

Comments
 (0)