Thanks to visit codestin.com
Credit goes to github.com

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

8 files changed

Lines changed: 165 additions & 10 deletions

File tree

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)