Skip to content

Commit 42c06bf

Browse files
authored
Merge pull request #226 from github/hvitved/const-flow
Data flow through constants
2 parents 64a55ba + 9463927 commit 42c06bf

10 files changed

Lines changed: 261 additions & 255 deletions

File tree

ql/src/codeql_ruby/ast/Constant.qll

Lines changed: 30 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,35 @@ 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+
this.hasGlobalScope() and
118+
result = lookupConst(TResolved("Object"), this.getName())
119+
or
120+
result = lookupConst(resolveScopeExpr(this.getScopeExpr()), this.getName())
121+
}
122+
93123
final override string getAPrimaryQlClass() { result = "ConstantReadAccess" }
94124
}
95125

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: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,21 @@ 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) {
139+
TExpr(result) = lookupMethodOrConst(m, name)
140+
or
141+
exists(AssignExpr ae, ConstantWriteAccess w |
142+
w = ae.getLeftOperand() and
143+
w.getName() = name and
144+
m = resolveScopeExpr(w.getScopeExpr()) and
145+
result = ae.getRightOperand()
146+
)
147+
}
133148
}
134149

135150
import Cached
@@ -340,24 +355,47 @@ private Module getAncestors(Module m) {
340355
result = getAncestors(m.getAPrependedModule())
341356
}
342357

343-
Method getMethod(TModule owner, string name) {
344-
exists(ModuleBase m | m.getModule() = owner and result = m.getMethod(name))
358+
private newtype TMethodOrExpr =
359+
TMethod(Method m) or
360+
TExpr(Expr e)
361+
362+
private TMethodOrExpr getMethodOrConst(TModule owner, string name) {
363+
exists(ModuleBase m | m.getModule() = owner |
364+
result = TMethod(m.getMethod(name))
365+
or
366+
result = TExpr(m.getConstant(name))
367+
)
345368
}
346369

347-
private Method lookupMethod0(Module m, string name) {
348-
result = lookupMethod0(m.getAPrependedModule(), name)
370+
module ExposedForTestingOnly {
371+
Method getMethod(TModule owner, string name) { TMethod(result) = getMethodOrConst(owner, name) }
372+
373+
Expr getConst(TModule owner, string name) { TExpr(result) = getMethodOrConst(owner, name) }
374+
}
375+
376+
private TMethodOrExpr lookupMethodOrConst0(Module m, string name) {
377+
result = lookupMethodOrConst0(m.getAPrependedModule(), name)
349378
or
350-
not exists(getMethod(getAncestors(m.getAPrependedModule()), name)) and
379+
not exists(getMethodOrConst(getAncestors(m.getAPrependedModule()), name)) and
351380
(
352-
result = getMethod(m, name)
381+
result = getMethodOrConst(m, name)
353382
or
354-
not exists(getMethod(m, name)) and result = lookupMethod0(m.getAnIncludedModule(), name)
383+
not exists(getMethodOrConst(m, name)) and
384+
result = lookupMethodOrConst0(m.getAnIncludedModule(), name)
355385
)
356386
}
357387

358-
Method lookupMethod(Module m, string name) {
359-
result = lookupMethod0(m, name)
388+
private AstNode getNode(TMethodOrExpr e) { e = TMethod(result) or e = TExpr(result) }
389+
390+
private TMethodOrExpr lookupMethodOrConst(Module m, string name) {
391+
result = lookupMethodOrConst0(m, name)
360392
or
361-
not exists(lookupMethod0(m, name)) and
362-
result = lookupMethod(m.getSuperClass(), name)
393+
not exists(lookupMethodOrConst0(m, name)) and
394+
result = lookupMethodOrConst(m.getSuperClass(), name) and
395+
// For now, we restrict the scope of top-level declarations to their file.
396+
// This may remove some plausible targets, but also removes a lot of
397+
// implausible targets
398+
if getNode(result).getEnclosingModule() instanceof Toplevel
399+
then getNode(result).getFile() = m.getADeclaration().getFile()
400+
else any()
363401
}

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/Ast.expected

Lines changed: 74 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -803,53 +803,81 @@ constants/constants.rb:
803803
# 1| [Toplevel] constants.rb
804804
# 1| getStmt: [ModuleDeclaration] ModuleA
805805
# 2| getStmt: [ClassDeclaration] ClassA
806-
# 5| getStmt: [ModuleDeclaration] ModuleB
807-
# 6| getStmt: [ClassDeclaration] ClassB
808-
# 6| getSuperclassExpr: [ConstantReadAccess] Base
809-
# 9| getStmt: [ClassDeclaration] ClassC
810-
# 9| getSuperclassExpr: [ConstantReadAccess] Z
811-
# 9| getScopeExpr: [ConstantReadAccess] Y
812-
# 9| getScopeExpr: [ConstantReadAccess] X
813-
# 14| getStmt: [AssignExpr] ... = ...
814-
# 14| getAnOperand/getLeftOperand: [ConstantAssignment] GREETING
815-
# 14| getAnOperand/getRightOperand: [StringLiteral] "Hello"
816-
# 14| getComponent: [StringTextComponent] Hello
817-
# 16| getStmt: [Method] foo
818-
# 17| getStmt: [AssignExpr] ... = ...
819-
# 17| getAnOperand/getLeftOperand: [ConstantAssignment] Names
820-
# 17| getAnOperand/getRightOperand: [ArrayLiteral] [...]
821-
# 17| getElement: [StringLiteral] "Vera"
822-
# 17| getComponent: [StringTextComponent] Vera
823-
# 17| getElement: [StringLiteral] "Chuck"
824-
# 17| getComponent: [StringTextComponent] Chuck
825-
# 17| getElement: [StringLiteral] "Dave"
826-
# 17| getComponent: [StringTextComponent] Dave
827-
# 19| getStmt: [MethodCall] call to each
828-
# 19| getReceiver: [ConstantReadAccess] Names
829-
# 19| getBlock: [DoBlock] do ... end
830-
# 19| getParameter: [SimpleParameter] name
831-
# 19| getDefiningAccess: [LocalVariableAccess] name
832-
# 20| getStmt: [MethodCall] call to puts
833-
# 20| getReceiver: [Self] self
834-
# 20| getArgument: [StringLiteral] "#{...} #{...}"
835-
# 20| getComponent: [StringInterpolationComponent] #{...}
836-
# 20| getStmt: [ConstantReadAccess] GREETING
837-
# 20| getComponent: [StringTextComponent]
838-
# 20| getComponent: [StringInterpolationComponent] #{...}
839-
# 20| getStmt: [LocalVariableAccess] name
840-
# 25| getStmt: [MethodCall] call to Array
841-
# 25| getReceiver: [Self] self
842-
# 25| getArgument: [StringLiteral] "foo"
843-
# 25| getComponent: [StringTextComponent] foo
844-
# 28| getStmt: [ClassDeclaration] ClassD
845-
# 28| getScopeExpr: [ConstantReadAccess] ModuleA
846-
# 31| getStmt: [ModuleDeclaration] ModuleC
806+
# 3| getStmt: [AssignExpr] ... = ...
807+
# 3| getAnOperand/getLeftOperand: [ConstantAssignment] CONST_A
808+
# 3| getAnOperand/getRightOperand: [StringLiteral] "const_a"
809+
# 3| getComponent: [StringTextComponent] const_a
810+
# 6| getStmt: [AssignExpr] ... = ...
811+
# 6| getAnOperand/getLeftOperand: [ConstantAssignment] CONST_B
812+
# 6| getAnOperand/getRightOperand: [StringLiteral] "const_b"
813+
# 6| getComponent: [StringTextComponent] const_b
814+
# 8| getStmt: [ModuleDeclaration] ModuleB
815+
# 9| getStmt: [ClassDeclaration] ClassB
816+
# 9| getSuperclassExpr: [ConstantReadAccess] Base
817+
# 12| getStmt: [ClassDeclaration] ClassC
818+
# 12| getSuperclassExpr: [ConstantReadAccess] Z
819+
# 12| getScopeExpr: [ConstantReadAccess] Y
820+
# 12| getScopeExpr: [ConstantReadAccess] X
821+
# 17| getStmt: [AssignExpr] ... = ...
822+
# 17| getAnOperand/getLeftOperand: [ConstantAssignment] GREETING
823+
# 17| getAnOperand/getRightOperand: [AddExpr] ... + ...
824+
# 17| getAnOperand/getLeftOperand: [AddExpr] ... + ...
825+
# 17| getAnOperand/getLeftOperand: [StringLiteral] "Hello"
826+
# 17| getComponent: [StringTextComponent] Hello
827+
# 17| getAnOperand/getRightOperand: [ConstantReadAccess] CONST_A
828+
# 17| getScopeExpr: [ConstantReadAccess] ClassA
829+
# 17| getScopeExpr: [ConstantReadAccess] ModuleA
830+
# 17| getAnOperand/getRightOperand: [ConstantReadAccess] CONST_B
831+
# 17| getScopeExpr: [ConstantReadAccess] ModuleA
832+
# 19| getStmt: [Method] foo
833+
# 20| getStmt: [AssignExpr] ... = ...
834+
# 20| getAnOperand/getLeftOperand: [ConstantAssignment] Names
835+
# 20| getAnOperand/getRightOperand: [ArrayLiteral] [...]
836+
# 20| getElement: [StringLiteral] "Vera"
837+
# 20| getComponent: [StringTextComponent] Vera
838+
# 20| getElement: [StringLiteral] "Chuck"
839+
# 20| getComponent: [StringTextComponent] Chuck
840+
# 20| getElement: [StringLiteral] "Dave"
841+
# 20| getComponent: [StringTextComponent] Dave
842+
# 22| getStmt: [MethodCall] call to each
843+
# 22| getReceiver: [ConstantReadAccess] Names
844+
# 22| getBlock: [DoBlock] do ... end
845+
# 22| getParameter: [SimpleParameter] name
846+
# 22| getDefiningAccess: [LocalVariableAccess] name
847+
# 23| getStmt: [MethodCall] call to puts
848+
# 23| getReceiver: [Self] self
849+
# 23| getArgument: [StringLiteral] "#{...} #{...}"
850+
# 23| getComponent: [StringInterpolationComponent] #{...}
851+
# 23| getStmt: [ConstantReadAccess] GREETING
852+
# 23| getComponent: [StringTextComponent]
853+
# 23| getComponent: [StringInterpolationComponent] #{...}
854+
# 23| getStmt: [LocalVariableAccess] name
855+
# 28| getStmt: [MethodCall] call to Array
856+
# 28| getReceiver: [Self] self
857+
# 28| getArgument: [StringLiteral] "foo"
858+
# 28| getComponent: [StringTextComponent] foo
859+
# 31| getStmt: [ClassDeclaration] ClassD
847860
# 31| getScopeExpr: [ConstantReadAccess] ModuleA
848-
# 34| getStmt: [AssignExpr] ... = ...
849-
# 34| getAnOperand/getLeftOperand: [ConstantAssignment] MAX_SIZE
850-
# 34| getScopeExpr: [ConstantReadAccess] ModuleB
851-
# 34| getScopeExpr: [ConstantReadAccess] ModuleA
852-
# 34| getAnOperand/getRightOperand: [IntegerLiteral] 1024
861+
# 31| getSuperclassExpr: [ConstantReadAccess] ClassA
862+
# 31| getScopeExpr: [ConstantReadAccess] ModuleA
863+
# 34| getStmt: [ModuleDeclaration] ModuleC
864+
# 34| getScopeExpr: [ConstantReadAccess] ModuleA
865+
# 37| getStmt: [AssignExpr] ... = ...
866+
# 37| getAnOperand/getLeftOperand: [ConstantAssignment] MAX_SIZE
867+
# 37| getScopeExpr: [ConstantReadAccess] ModuleB
868+
# 37| getScopeExpr: [ConstantReadAccess] ModuleA
869+
# 37| getAnOperand/getRightOperand: [IntegerLiteral] 1024
870+
# 39| getStmt: [MethodCall] call to puts
871+
# 39| getReceiver: [Self] self
872+
# 39| getArgument: [ConstantReadAccess] MAX_SIZE
873+
# 39| getScopeExpr: [ConstantReadAccess] ModuleB
874+
# 39| getScopeExpr: [ConstantReadAccess] ModuleA
875+
# 41| getStmt: [MethodCall] call to puts
876+
# 41| getReceiver: [Self] self
877+
# 41| getArgument: [ConstantReadAccess] GREETING
878+
# 42| getStmt: [MethodCall] call to puts
879+
# 42| getReceiver: [Self] self
880+
# 42| getArgument: [ConstantReadAccess] GREETING
853881
literals/literals.rb:
854882
# 1| [Toplevel] literals.rb
855883
# 2| getStmt: [NilLiteral] nil
Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,56 @@
1-
| constants.rb:1:1:12:3 | ModuleA | write | ModuleA | ModuleDeclaration |
2-
| constants.rb:2:5:3:7 | ClassA | write | ClassA | ClassDeclaration |
3-
| constants.rb:5:5:11:7 | ModuleB | write | ModuleB | ModuleDeclaration |
4-
| constants.rb:6:9:7:11 | ClassB | write | ClassB | ClassDeclaration |
5-
| constants.rb:6:24:6:27 | Base | read | Base | ConstantReadAccess |
6-
| constants.rb:9:9:10:11 | ClassC | write | ClassC | ClassDeclaration |
7-
| constants.rb:9:24:9:24 | X | read | X | ConstantReadAccess |
8-
| constants.rb:9:24:9:27 | Y | read | Y | ConstantReadAccess |
9-
| constants.rb:9:24:9:30 | Z | read | Z | ConstantReadAccess |
10-
| constants.rb:14:1:14:8 | GREETING | write | GREETING | ConstantAssignment |
11-
| constants.rb:17:5:17:9 | Names | write | Names | ConstantAssignment |
12-
| constants.rb:19:5:19:9 | Names | read | Names | ConstantReadAccess |
13-
| constants.rb:20:18:20:25 | GREETING | read | GREETING | ConstantReadAccess |
14-
| constants.rb:28:1:29:3 | ClassD | write | ClassD | ClassDeclaration |
15-
| constants.rb:28:7:28:13 | ModuleA | read | ModuleA | ConstantReadAccess |
16-
| constants.rb:31:1:32:3 | ModuleC | write | ModuleC | ModuleDeclaration |
17-
| constants.rb:31:8:31:14 | ModuleA | read | ModuleA | ConstantReadAccess |
18-
| constants.rb:34:1:34:7 | ModuleA | read | ModuleA | ConstantReadAccess |
19-
| constants.rb:34:1:34:16 | ModuleB | read | ModuleB | ConstantReadAccess |
20-
| constants.rb:34:1:34:26 | MAX_SIZE | write | MAX_SIZE | ConstantAssignment |
1+
constantAccess
2+
| constants.rb:1:1:15:3 | ModuleA | write | ModuleA | ModuleDeclaration |
3+
| constants.rb:2:5:4:7 | ClassA | write | ClassA | ClassDeclaration |
4+
| constants.rb:3:9:3:15 | CONST_A | write | CONST_A | ConstantAssignment |
5+
| constants.rb:6:5:6:11 | CONST_B | write | CONST_B | ConstantAssignment |
6+
| constants.rb:8:5:14:7 | ModuleB | write | ModuleB | ModuleDeclaration |
7+
| constants.rb:9:9:10:11 | ClassB | write | ClassB | ClassDeclaration |
8+
| constants.rb:9:24:9:27 | Base | read | Base | ConstantReadAccess |
9+
| constants.rb:12:9:13:11 | ClassC | write | ClassC | ClassDeclaration |
10+
| constants.rb:12:24:12:24 | X | read | X | ConstantReadAccess |
11+
| constants.rb:12:24:12:27 | Y | read | Y | ConstantReadAccess |
12+
| constants.rb:12:24:12:30 | Z | read | Z | ConstantReadAccess |
13+
| constants.rb:17:1:17:8 | GREETING | write | GREETING | ConstantAssignment |
14+
| constants.rb:17:22:17:28 | ModuleA | read | ModuleA | ConstantReadAccess |
15+
| constants.rb:17:22:17:36 | ClassA | read | ClassA | ConstantReadAccess |
16+
| constants.rb:17:22:17:45 | CONST_A | read | CONST_A | ConstantReadAccess |
17+
| constants.rb:17:49:17:55 | ModuleA | read | ModuleA | ConstantReadAccess |
18+
| constants.rb:17:49:17:64 | CONST_B | read | CONST_B | ConstantReadAccess |
19+
| constants.rb:20:5:20:9 | Names | write | Names | ConstantAssignment |
20+
| constants.rb:22:5:22:9 | Names | read | Names | ConstantReadAccess |
21+
| constants.rb:23:18:23:25 | GREETING | read | GREETING | ConstantReadAccess |
22+
| constants.rb:31:1:32:3 | ClassD | write | ClassD | ClassDeclaration |
23+
| constants.rb:31:7:31:13 | ModuleA | read | ModuleA | ConstantReadAccess |
24+
| constants.rb:31:25:31:31 | ModuleA | read | ModuleA | ConstantReadAccess |
25+
| constants.rb:31:25:31:39 | ClassA | read | ClassA | ConstantReadAccess |
26+
| constants.rb:34:1:35:3 | ModuleC | write | ModuleC | ModuleDeclaration |
27+
| constants.rb:34:8:34:14 | ModuleA | read | ModuleA | ConstantReadAccess |
28+
| constants.rb:37:1:37:7 | ModuleA | read | ModuleA | ConstantReadAccess |
29+
| constants.rb:37:1:37:16 | ModuleB | read | ModuleB | ConstantReadAccess |
30+
| constants.rb:37:1:37:26 | MAX_SIZE | write | MAX_SIZE | ConstantAssignment |
31+
| constants.rb:39:6:39:12 | ModuleA | read | ModuleA | ConstantReadAccess |
32+
| constants.rb:39:6:39:21 | ModuleB | read | ModuleB | ConstantReadAccess |
33+
| constants.rb:39:6:39:31 | MAX_SIZE | read | MAX_SIZE | ConstantReadAccess |
34+
| constants.rb:41:6:41:13 | GREETING | read | GREETING | ConstantReadAccess |
35+
| constants.rb:42:6:42:15 | GREETING | read | GREETING | ConstantReadAccess |
36+
getConst
37+
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
38+
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
39+
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
40+
lookupConst
41+
| constants.rb:1:1:15:3 | ModuleA | CONST_B | constants.rb:6:15:6:23 | "const_b" |
42+
| constants.rb:2:5:4:7 | ModuleA::ClassA | CONST_A | constants.rb:3:19:3:27 | "const_a" |
43+
| constants.rb:2:5:4:7 | ModuleA::ClassA | GREETING | constants.rb:17:12:17:64 | ... + ... |
44+
| constants.rb:8:5:14:7 | ModuleA::ModuleB | MAX_SIZE | constants.rb:37:30:37:33 | 1024 |
45+
| constants.rb:9:9:10:11 | ModuleA::ModuleB::ClassB | GREETING | constants.rb:17:12:17:64 | ... + ... |
46+
| constants.rb:12:9:13:11 | ModuleA::ModuleB::ClassC | GREETING | constants.rb:17:12:17:64 | ... + ... |
47+
| constants.rb:31:1:32:3 | ModuleA::ClassD | CONST_A | constants.rb:3:19:3:27 | "const_a" |
48+
| constants.rb:31:1:32:3 | ModuleA::ClassD | GREETING | constants.rb:17:12:17:64 | ... + ... |
49+
| file://:0:0:0:0 | Object | GREETING | constants.rb:17:12:17:64 | ... + ... |
50+
constantValue
51+
| constants.rb:17:22:17:45 | CONST_A | constants.rb:3:19:3:27 | "const_a" |
52+
| constants.rb:17:49:17:64 | CONST_B | constants.rb:6:15:6:23 | "const_b" |
53+
| constants.rb:23:18:23:25 | GREETING | constants.rb:17:12:17:64 | ... + ... |
54+
| constants.rb:39:6:39:31 | MAX_SIZE | constants.rb:37:30:37:33 | 1024 |
55+
| constants.rb:41:6:41:13 | GREETING | constants.rb:17:12:17:64 | ... + ... |
56+
| constants.rb:42:6:42:15 | GREETING | constants.rb:17:12:17:64 | ... + ... |
Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
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) }
17+
18+
query predicate constantValue(ConstantReadAccess a, Expr e) { e = a.getValue() }

0 commit comments

Comments
 (0)