Skip to content

Commit 51d5e88

Browse files
committed
add an Ast mocking library, and use it to create mocks of the QlBuiltins and EquivalenceRelation modules
1 parent 0ca38fa commit 51d5e88

File tree

8 files changed

+165
-10
lines changed

8 files changed

+165
-10
lines changed

ql/ql/src/codeql_ql/ast/Ast.qll

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ private import codeql_ql.ast.internal.Predicate
55
import codeql_ql.ast.internal.Type
66
private import codeql_ql.ast.internal.Variable
77
private import codeql_ql.ast.internal.Builtins
8+
private import internal.AstMocks as Mocks
89

910
bindingset[name]
1011
private string directMember(string name) { result = name + "()" }
@@ -779,34 +780,44 @@ class TypeExpr extends TType, TypeRef {
779780
* A QL module.
780781
*/
781782
class Module extends TModule, ModuleDeclaration {
782-
QL::Module mod;
783+
Mocks::ModuleOrMock mod;
783784

784785
Module() { this = TModule(mod) }
785786

786-
override Location getLocation() { result = mod.getName().getLocation() }
787+
override Location getLocation() { result = mod.asLeft().getName().getLocation() }
787788

788789
override string getAPrimaryQlClass() { result = "Module" }
789790

790-
override string getName() { result = mod.getName().getChild().getValue() }
791+
override string getName() {
792+
result = mod.asLeft().getName().getChild().getValue()
793+
or
794+
result = mod.asRight().getName()
795+
}
791796

792797
/**
793798
* Gets a member of the module.
794799
*/
795-
AstNode getAMember() { toQL(result) = mod.getChild(_).(QL::ModuleMember).getChild(_) }
800+
AstNode getAMember() { result = getMember(_) }
796801

797-
AstNode getMember(int i) { toQL(result) = mod.getChild(i).(QL::ModuleMember).getChild(_) }
802+
AstNode getMember(int i) {
803+
toQL(result) = mod.asLeft().getChild(i).(QL::ModuleMember).getChild(_)
804+
or
805+
toMock(result) = mod.asRight().getMember(i)
806+
}
798807

799808
QLDoc getQLDocFor(AstNode m) {
800809
exists(int i | result = this.getMember(i) and m = this.getMember(i + 1))
801810
}
802811

803812
/** Gets a ref to the module that this module implements. */
804813
TypeRef getImplements(int i) {
805-
exists(SignatureExpr sig | sig.toQL() = mod.getImplements(i) | result = sig.asType())
814+
exists(SignatureExpr sig | sig.toQL() = mod.asLeft().getImplements(i) | result = sig.asType())
806815
}
807816

808817
/** Gets the module expression that this module is an alias for, if any. */
809-
ModuleExpr getAlias() { toQL(result) = mod.getAFieldOrChild().(QL::ModuleAliasBody).getChild() }
818+
ModuleExpr getAlias() {
819+
toQL(result) = mod.asLeft().getAFieldOrChild().(QL::ModuleAliasBody).getChild()
820+
}
810821

811822
override AstNode getAChild(string pred) {
812823
result = super.getAChild(pred)
@@ -823,10 +834,12 @@ class Module extends TModule, ModuleDeclaration {
823834
/** Holds if the `i`th parameter of this module has `name` and type `sig`. */
824835
predicate hasParameter(int i, string name, SignatureExpr sig) {
825836
exists(QL::ModuleParam param |
826-
param = mod.getParameter(i) and
837+
param = mod.asLeft().getParameter(i) and
827838
name = param.getParameter().getValue() and
828839
sig.toQL() = param.getSignature()
829840
)
841+
or
842+
mod.asRight().hasTypeParam(i, toMock(sig), name)
830843
}
831844
}
832845

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Implements AST mocks.
3+
*
4+
* Everything is mostly implemented using magic strings, because we don't have
5+
* bindingsets on IPA types.
6+
* Those strings have to be unique, even across different types of mock AST nodes.
7+
*/
8+
9+
private import codeql_ql.ast.internal.AstNodes
10+
private import codeql.util.Unit
11+
private import codeql.util.Either
12+
13+
// Three good reasons for doing an IPA type instead of just a string directly:
14+
// 1: Better type checking with distinct types.
15+
// 2: We don't get all the methods defined on strings, to confuse us.
16+
// 3: The Either type gets a type without bindingset on the charpred/toString.
17+
newtype TMockAst = TMockModule(string id) { id instanceof MockModule::Range }
18+
19+
/** Gets a mocked Ast node from the string ID that represents it. */
20+
MockAst fromId(string id) {
21+
result = TMockModule(id)
22+
// TODO: Other nodes.
23+
}
24+
25+
/** A mocked AST node. */
26+
class MockAst extends TMockAst {
27+
string toString() { fromId(result) = this }
28+
29+
string getId() { result = this.toString() }
30+
}
31+
32+
/**
33+
* A mocked module.
34+
* Extend `MockModule::Range` to add new mocked modules.
35+
*/
36+
class MockModule extends MockAst, TMockModule {
37+
MockModule::Range range;
38+
39+
MockModule() { this = TMockModule(range) }
40+
41+
final string getName() { result = range.getName() }
42+
43+
/** Gets the `i`th mocked child of this module. */
44+
final MockAst getMember(int i) { result = fromId(range.getMember(i)) }
45+
46+
final predicate hasTypeParam(int i, MockAst type, string name) {
47+
range.hasTypeParam(i, type.toString(), name)
48+
}
49+
}
50+
51+
module MockModule {
52+
abstract class Range extends string {
53+
bindingset[this]
54+
Range() { this = this }
55+
56+
/** Gets tthe name of this mocked module. */
57+
abstract string getName();
58+
59+
/** Gets the id for the `i`th mocked member of this module. */
60+
abstract string getMember(int i);
61+
62+
/** Holds if the `i`th type parameter has `type` (the ID of the mocked node) with `name`. */
63+
predicate hasTypeParam(int i, string type, string name) {
64+
none() // may be overridden in subclasses
65+
}
66+
}
67+
}
68+
69+
class ModuleOrMock = Either<QL::Module, MockModule>::Either;

ql/ql/src/codeql_ql/ast/internal/AstNodes.qll

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import codeql_ql.ast.Ast as AST
22
import TreeSitter
33
private import Builtins
4+
private import AstMocks as Mocks
45

56
cached
67
newtype TAstNode =
@@ -16,7 +17,7 @@ newtype TAstNode =
1617
TClassPredicate(QL::MemberPredicate pred) or
1718
TDBRelation(Dbscheme::Table table) or
1819
TSelect(QL::Select sel) or
19-
TModule(QL::Module mod) or
20+
TModule(Mocks::ModuleOrMock mod) or
2021
TNewType(QL::Datatype dt) or
2122
TNewTypeBranch(QL::DatatypeBranch branch) or
2223
TImport(QL::ImportDirective imp) or
@@ -173,7 +174,7 @@ QL::AstNode toQL(AST::AstNode n) {
173174
or
174175
n = TSelect(result)
175176
or
176-
n = TModule(result)
177+
n = TModule(any(Mocks::ModuleOrMock m | m.asLeft() = result))
177178
or
178179
n = TNewType(result)
179180
or
@@ -206,6 +207,12 @@ QL::AstNode toQL(AST::AstNode n) {
206207
n = TAnnotationArg(result)
207208
}
208209

210+
Mocks::MockAst toMock(AST::AstNode n) {
211+
n = TModule(any(Mocks::ModuleOrMock m | m.asRight() = result))
212+
or
213+
none() // TODO: Remove, this is to loosen the type of `toMock` to avoid type-errors in WIP code.
214+
}
215+
209216
class TPredicate =
210217
TCharPred or TClasslessPredicate or TClassPredicate or TDBRelation or TNewTypeBranch;
211218

ql/ql/src/codeql_ql/ast/internal/Builtins.qll

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,49 @@ class FloatClass extends PrimitiveType {
9494
class BooleanClass extends PrimitiveType {
9595
BooleanClass() { this.getName() = "boolean" }
9696
}
97+
98+
/**
99+
* Implements mocks for the built-in modules inside the `QlBuiltins` module.
100+
*/
101+
module QlBuiltinsMocks {
102+
private import AstMocks
103+
104+
class QlBuiltinsModule extends MockModule::Range {
105+
QlBuiltinsModule() { this = "Mock: QlBuiltins" }
106+
107+
override string getName() { result = "QlBuiltins" }
108+
109+
override MockModule::Range getMember(int i) {
110+
// TODO: T, InstSig
111+
i = 2 and
112+
result instanceof EquivalenceRelation::EquivalenceRelationModule
113+
}
114+
}
115+
116+
/**
117+
* A mock that implements the `EquivalenceRelation` module.
118+
* The equivalent to the following is implemented: (TODO: WIP, MISSING THE LINES WITH //)
119+
* ```CodeQL
120+
* module QlBuiltins {
121+
* signature class T; //
122+
* module InstSig<T MyT> { //
123+
* signature predicate edgeSig(MyT a, MyT b); //
124+
* }
125+
* module EquivalenceRelation<T MyT, InstSig<MyT>::edgeSig/2 edge> { //
126+
* class EquivalenceClass; //
127+
* EquivalenceClass getEquivalenceClass(MyT a); //
128+
* }
129+
*}
130+
*/
131+
module EquivalenceRelation {
132+
class EquivalenceRelationModule extends MockModule::Range {
133+
EquivalenceRelationModule() { this = "Mock: QlBuiltins::EquivalenceRelation" }
134+
135+
override string getName() { result = "EquivalenceRelation" }
136+
137+
override MockModule::Range getMember(int i) {
138+
none() // TODO: EquivalenceClass/getEquivalenceClass
139+
}
140+
}
141+
}
142+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ private module Cached {
194194
TModule(Module m)
195195
}
196196

197+
private import codeql_ql.ast.internal.Builtins as Builtins
198+
197199
/** Holds if module expression `me` resolves to `m`. */
198200
cached
199201
predicate resolveModuleRef(TypeRef me, FileOrModule m) {
@@ -223,6 +225,12 @@ private module Cached {
223225
resolveModuleRef(me.(ModuleExpr).getQualifier(), mid) and
224226
definesModule(mid, me.(ModuleExpr).getName(), m, true)
225227
)
228+
or
229+
// The buildin `QlBuiltins` module, implemented as a mock AST node.
230+
me instanceof ModuleExpr and
231+
not exists(me.(ModuleExpr).getQualifier()) and
232+
me.(ModuleExpr).getName() = "QlBuiltins" and
233+
AstNodes::toMock(m.asModule()).getId() instanceof Builtins::QlBuiltinsMocks::QlBuiltinsModule
226234
}
227235

228236
/**

ql/ql/src/qlpack.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ defaultSuiteFile: codeql-suites/ql-code-scanning.qls
77
extractor: ql
88
dependencies:
99
codeql/typos: ${workspace}
10+
codeql/util: ${workspace}

ql/ql/test/modules/test.expected

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ getTarget
88
| Foo.qll:19:38:19:52 | MkThing | Foo.qll:9:16:9:22 | MkThing |
99
| Foo.qll:19:38:19:60 | TypeExpr | Foo.qll:10:20:10:25 | SubMod |
1010
| Foo.qll:19:46:19:51 | TypeExpr | Foo.qll:5:16:5:21 | MyImpl |
11+
| Foo.qll:29:22:29:31 | QlBuiltins | file://:0:0:0:0 | QlBuiltins |
12+
| Foo.qll:29:22:29:69 | EquivalenceRelation | file://:0:0:0:0 | EquivalenceRelation |
1113
| Foo.qll:31:29:31:31 | Scc | Foo.qll:29:16:29:18 | Scc |
14+
| Foo.qll:31:29:31:31 | Scc | file://:0:0:0:0 | EquivalenceRelation |
1215
| Foo.qll:34:52:34:54 | Scc | Foo.qll:29:16:29:18 | Scc |
16+
| Foo.qll:34:52:34:54 | Scc | file://:0:0:0:0 | EquivalenceRelation |
1317
getTargetType
1418
| ClassSig.qll:3:23:3:28 | TypeExpr | file://:0:0:0:0 | string |
1519
| ClassSig.qll:7:12:7:17 | TypeExpr | ClassSig.qll:1:17:1:22 | FooSig |
@@ -28,8 +32,12 @@ getTargetType
2832
| Foo.qll:23:20:23:22 | TypeExpr | file://:0:0:0:0 | int |
2933
| Foo.qll:27:19:27:22 | TypeExpr | Foo.qll:23:7:23:10 | Node |
3034
| Foo.qll:27:27:27:30 | TypeExpr | Foo.qll:23:7:23:10 | Node |
35+
| Foo.qll:29:22:29:31 | QlBuiltins | file://:0:0:0:0 | QlBuiltins |
36+
| Foo.qll:29:22:29:69 | EquivalenceRelation | file://:0:0:0:0 | EquivalenceRelation |
3137
| Foo.qll:29:54:29:57 | TypeExpr | Foo.qll:23:7:23:10 | Node |
3238
| Foo.qll:31:29:31:31 | Scc | Foo.qll:29:16:29:18 | Scc |
39+
| Foo.qll:31:29:31:31 | Scc | file://:0:0:0:0 | EquivalenceRelation |
3340
| Foo.qll:34:19:34:22 | TypeExpr | Foo.qll:23:7:23:10 | Node |
3441
| Foo.qll:34:52:34:54 | Scc | Foo.qll:29:16:29:18 | Scc |
42+
| Foo.qll:34:52:34:54 | Scc | file://:0:0:0:0 | EquivalenceRelation |
3543
| Foo.qll:36:23:36:26 | TypeExpr | Foo.qll:23:7:23:10 | Node |

ql/ql/test/printAst/printAst.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ nodes
161161
| Foo.qll:30:10:30:27 | assume_small_delta | semmle.order | 80 |
162162
| Foo.qll:31:3:31:14 | NewTypeBranch TPathNodeMid | semmle.label | [NewTypeBranch] NewTypeBranch TPathNodeMid |
163163
| Foo.qll:31:3:31:14 | NewTypeBranch TPathNodeMid | semmle.order | 81 |
164+
| file://:0:0:0:0 | Module EquivalenceRelation | semmle.label | [Module] Module EquivalenceRelation |
165+
| file://:0:0:0:0 | Module QlBuiltins | semmle.label | [Module] Module QlBuiltins |
164166
| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs |
165167
| file://:0:0:0:0 | abs | semmle.label | [BuiltinPredicate] abs |
166168
| file://:0:0:0:0 | acos | semmle.label | [BuiltinPredicate] acos |
@@ -409,6 +411,7 @@ edges
409411
| Foo.qll:30:3:30:28 | annotation | Foo.qll:30:10:30:27 | assume_small_delta | semmle.order | 80 |
410412
| Foo.qll:31:3:31:14 | NewTypeBranch TPathNodeMid | Foo.qll:30:3:30:28 | annotation | semmle.label | getAnAnnotation() |
411413
| Foo.qll:31:3:31:14 | NewTypeBranch TPathNodeMid | Foo.qll:30:3:30:28 | annotation | semmle.order | 79 |
414+
| file://:0:0:0:0 | Module QlBuiltins | file://:0:0:0:0 | Module EquivalenceRelation | semmle.label | getAMember() |
412415
| printAst.ql:1:1:1:28 | Import | printAst.ql:1:18:1:28 | printAstAst | semmle.label | getModuleExpr() |
413416
| printAst.ql:1:1:1:28 | Import | printAst.ql:1:18:1:28 | printAstAst | semmle.order | 84 |
414417
| printAst.ql:1:1:1:29 | TopLevel | printAst.ql:1:1:1:28 | Import | semmle.label | getAnImport() |

0 commit comments

Comments
 (0)