Skip to content

Commit bf696df

Browse files
committed
Data flow through constants
1 parent 3b6e588 commit bf696df

7 files changed

Lines changed: 124 additions & 18 deletions

File tree

ql/src/codeql_ruby/ast/Constant.qll

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
private import codeql_ruby.AST
22
private import internal.AST
3+
private import internal.Module
34
private import internal.Variable
45
private import internal.TreeSitter
56

@@ -90,6 +91,32 @@ class ConstantReadAccess extends ConstantAccess {
9091
this = any(AssignOperation a).getLeftOperand()
9192
}
9293

94+
/**
95+
* Gets the value being read, if any. For example, in
96+
*
97+
* ```rb
98+
* module M
99+
* CONST = "const"
100+
* end
101+
*
102+
* puts M::CONST
103+
* ```
104+
*
105+
* the value being read at `M::CONST` is `"const"`.
106+
*/
107+
Expr getValue() {
108+
not exists(this.getScopeExpr()) and
109+
result = lookupConst(this.getEnclosingModule+().getModule(), this.getName()) and
110+
// For now, we restrict the scope of top-level declarations to their file.
111+
// This may remove some plausible targets, but also removes a lot of
112+
// implausible targets
113+
if result.getEnclosingModule() instanceof Toplevel
114+
then result.getFile() = this.getFile()
115+
else any()
116+
or
117+
result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName())
118+
}
119+
93120
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
94121
}
95122

ql/src/codeql_ruby/ast/Module.qll

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,26 @@ class ModuleBase extends BodyStmt, Scope, TModuleBase {
7474
result = this.getAModule() and result.getName() = name
7575
}
7676

77+
/**
78+
* Gets the value of the constant named `name`, if any.
79+
*
80+
* For example, the value of `CONST` is `"const"` in
81+
* ```rb
82+
* module M
83+
* CONST = "const"
84+
* end
85+
* ```
86+
*/
87+
Expr getConstant(string name) {
88+
exists(AssignExpr ae, ConstantWriteAccess w |
89+
ae = this.getAStmt() and
90+
w = ae.getLeftOperand() and
91+
w.getName() = name and
92+
not exists(w.getScopeExpr()) and
93+
result = ae.getRightOperand()
94+
)
95+
}
96+
7797
/** Gets the representation of the run-time value of this module or class. */
7898
Module getModule() { none() }
7999
}

ql/src/codeql_ruby/ast/internal/Module.qll

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ private module Cached {
130130
)
131131
)
132132
}
133+
134+
cached
135+
Method lookupMethod(Module m, string name) { TMethod(result) = lookupMethodOrConst(m, name) }
136+
137+
cached
138+
Expr lookupConst(Module m, string name) { TExpr(result) = lookupMethodOrConst(m, name) }
133139
}
134140

135141
import Cached
@@ -340,24 +346,39 @@ private Module getAncestors(Module m) {
340346
result = getAncestors(m.getAPrependedModule())
341347
}
342348

343-
Method getMethod(TModule owner, string name) {
344-
exists(ModuleBase m | m.getModule() = owner and result = m.getMethod(name))
349+
private newtype TMethodOrExpr =
350+
TMethod(Method m) or
351+
TExpr(Expr e)
352+
353+
private TMethodOrExpr getMethodOrConst(TModule owner, string name) {
354+
exists(ModuleBase m | m.getModule() = owner |
355+
result = TMethod(m.getMethod(name))
356+
or
357+
result = TExpr(m.getConstant(name))
358+
)
359+
}
360+
361+
module ExposedForTestingOnly {
362+
Method getMethod(TModule owner, string name) { TMethod(result) = getMethodOrConst(owner, name) }
363+
364+
Expr getConst(TModule owner, string name) { TExpr(result) = getMethodOrConst(owner, name) }
345365
}
346366

347-
private Method lookupMethod0(Module m, string name) {
348-
result = lookupMethod0(m.getAPrependedModule(), name)
367+
private TMethodOrExpr lookupMethodOrConst0(Module m, string name) {
368+
result = lookupMethodOrConst0(m.getAPrependedModule(), name)
349369
or
350-
not exists(getMethod(getAncestors(m.getAPrependedModule()), name)) and
370+
not exists(getMethodOrConst(getAncestors(m.getAPrependedModule()), name)) and
351371
(
352-
result = getMethod(m, name)
372+
result = getMethodOrConst(m, name)
353373
or
354-
not exists(getMethod(m, name)) and result = lookupMethod0(m.getAnIncludedModule(), name)
374+
not exists(getMethodOrConst(m, name)) and
375+
result = lookupMethodOrConst0(m.getAnIncludedModule(), name)
355376
)
356377
}
357378

358-
Method lookupMethod(Module m, string name) {
359-
result = lookupMethod0(m, name)
379+
private TMethodOrExpr lookupMethodOrConst(Module m, string name) {
380+
result = lookupMethodOrConst0(m, name)
360381
or
361-
not exists(lookupMethod0(m, name)) and
362-
result = lookupMethod(m.getSuperClass(), name)
382+
not exists(lookupMethodOrConst0(m, name)) and
383+
result = lookupMethodOrConst(m.getSuperClass(), name)
363384
}

ql/src/codeql_ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,8 @@ predicate jumpStep(Node pred, Node succ) {
455455
m = s.getEnclosingMethod() and
456456
m != s.getEnclosingCallable()
457457
)
458+
or
459+
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
458460
}
459461

460462
predicate storeStep(Node node1, Content c, Node node2) { none() }

ql/test/library-tests/ast/constants/constants.expected

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
constantAccess
12
| constants.rb:1:1:15:3 | ModuleA | write | ModuleA | ModuleDeclaration |
23
| constants.rb:2:5:4:7 | ClassA | write | ClassA | ClassDeclaration |
34
| constants.rb:3:9:3:15 | CONST_A | write | CONST_A | ConstantAssignment |
@@ -27,3 +28,28 @@
2728
| constants.rb:37:1:37:7 | ModuleA | read | ModuleA | ConstantReadAccess |
2829
| constants.rb:37:1:37:16 | ModuleB | read | ModuleB | ConstantReadAccess |
2930
| constants.rb:37:1:37:26 | MAX_SIZE | write | MAX_SIZE | ConstantAssignment |
31+
getConst
32+
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
33+
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
34+
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
35+
lookupConst
36+
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
37+
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
38+
| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... |
39+
| constants.rb:9:9:10:11 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... |
40+
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... |
41+
| constants.rb:31:1:32:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" |
42+
| constants.rb:31:1:32:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... |
43+
| file://:0:0:0:0 | Array | GREETING | constants.rb:17:12:17:64 | ... + ... |
44+
| file://:0:0:0:0 | Class | GREETING | constants.rb:17:12:17:64 | ... + ... |
45+
| file://:0:0:0:0 | Complex | GREETING | constants.rb:17:12:17:64 | ... + ... |
46+
| file://:0:0:0:0 | FalseClass | GREETING | constants.rb:17:12:17:64 | ... + ... |
47+
| file://:0:0:0:0 | Float | GREETING | constants.rb:17:12:17:64 | ... + ... |
48+
| file://:0:0:0:0 | Hash | GREETING | constants.rb:17:12:17:64 | ... + ... |
49+
| file://:0:0:0:0 | Integer | GREETING | constants.rb:17:12:17:64 | ... + ... |
50+
| file://:0:0:0:0 | Module | GREETING | constants.rb:17:12:17:64 | ... + ... |
51+
| file://:0:0:0:0 | NilClass | GREETING | constants.rb:17:12:17:64 | ... + ... |
52+
| file://:0:0:0:0 | Numeric | GREETING | constants.rb:17:12:17:64 | ... + ... |
53+
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
54+
| file://:0:0:0:0 | Rational | GREETING | constants.rb:17:12:17:64 | ... + ... |
55+
| file://:0:0:0:0 | TrueClass | GREETING | constants.rb:17:12:17:64 | ... + ... |
Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11
import ruby
2+
import codeql_ruby.ast.internal.Module as M
23

3-
from ConstantAccess a, string kind
4-
where
5-
a instanceof ConstantReadAccess and kind = "read"
6-
or
7-
a instanceof ConstantWriteAccess and kind = "write"
8-
select a, kind, a.getName(), a.getAPrimaryQlClass()
4+
query predicate constantAccess(ConstantAccess a, string kind, string name, string cls) {
5+
(
6+
a instanceof ConstantReadAccess and kind = "read"
7+
or
8+
a instanceof ConstantWriteAccess and kind = "write"
9+
) and
10+
name = a.getName() and
11+
cls = a.getAPrimaryQlClass()
12+
}
13+
14+
query Expr getConst(Module m, string name) { result = M::ExposedForTestingOnly::getConst(m, name) }
15+
16+
query Expr lookupConst(Module m, string name) { result = M::lookupConst(m, name) }
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import ruby
22
import codeql_ruby.ast.internal.Module as M
33

4-
query MethodBase getMethod(Module m, string name) { result = M::getMethod(m, name) }
4+
query MethodBase getMethod(Module m, string name) {
5+
result = M::ExposedForTestingOnly::getMethod(m, name)
6+
}
57

68
query MethodBase lookupMethod(Module m, string name) { result = M::lookupMethod(m, name) }

0 commit comments

Comments
 (0)