Skip to content

Commit c97001e

Browse files
committed
add support for module instantiations in import statements. Rework the import resolution logic to reuse the logic from ModuleExpr
1 parent 641c6b0 commit c97001e

File tree

16 files changed

+373
-277
lines changed

16 files changed

+373
-277
lines changed

ql/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ql/extractor/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ edition = "2018"
1010
flate2 = "1.0"
1111
node-types = { path = "../node-types" }
1212
tree-sitter = ">= 0.20, < 0.21"
13-
tree-sitter-ql = { git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "dcf0139af9908dbc53efa96408b017133d19e6b2"}
13+
tree-sitter-ql = { git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "13155b740d4040e74727a9a698277805498a6a85"}
1414
tree-sitter-ql-dbscheme = { git = "https://github.com/erik-krogh/tree-sitter-ql-dbscheme.git", rev = "63e1344353f63931e88bfbc2faa2e78e1421b213"}
1515
tree-sitter-ql-yaml = {git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "cf704bf3671e1ae148e173464fb65a4d2bbf5f99"}
1616
clap = "2.33"

ql/generator/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,6 @@ clap = "2.33"
1111
node-types = { path = "../node-types" }
1212
tracing = "0.1"
1313
tracing-subscriber = { version = "0.3.3", features = ["env-filter"] }
14-
tree-sitter-ql = { git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "dcf0139af9908dbc53efa96408b017133d19e6b2"}
14+
tree-sitter-ql = { git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "13155b740d4040e74727a9a698277805498a6a85"}
1515
tree-sitter-ql-dbscheme = { git = "https://github.com/erik-krogh/tree-sitter-ql-dbscheme.git", rev = "63e1344353f63931e88bfbc2faa2e78e1421b213"}
1616
tree-sitter-ql-yaml = {git = "https://github.com/erik-krogh/tree-sitter-ql.git", rev = "cf704bf3671e1ae148e173464fb65a4d2bbf5f99"}

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

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1175,13 +1175,27 @@ class Import extends TImport, ModuleMember, TypeRef {
11751175
string importedAs() { result = imp.getChild(1).(QL::ModuleName).getChild().getValue() }
11761176

11771177
/**
1178-
* Gets the `i`th selected name from the imported module.
1178+
* Gets the qualified name of the module selected in the import statement.
11791179
* E.g. for
11801180
* `import foo.bar::Baz::Qux`
1181-
* It is true that `getSelectionName(0) = "Baz"` and `getSelectionName(1) = "Qux"`.
1181+
* It is true that `getSelectionName() = "Baz::Qux"`.
1182+
*
1183+
* Does NOT include type arguments!
1184+
*/
1185+
string getSelectionName() { result = getModuleExpr().getQualifiedName() }
1186+
1187+
/**
1188+
* Gets the module expression selected in the import statement.
1189+
* E.g. for
1190+
* `import foo.Bar::Baz::Qux`
1191+
* The module expression is the `Bar::Baz::Qux` part.
11821192
*/
1183-
string getSelectionName(int i) {
1184-
result = imp.getChild(0).(QL::ImportModuleExpr).getName(i).getValue()
1193+
ModuleExpr getModuleExpr() { toQL(result) = imp.getChild(0).(QL::ImportModuleExpr).getChild() }
1194+
1195+
override AstNode getAChild(string pred) {
1196+
result = super.getAChild(pred)
1197+
or
1198+
pred = directMember("getModuleExpr") and result = this.getModuleExpr()
11851199
}
11861200

11871201
/**
@@ -1191,27 +1205,25 @@ class Import extends TImport, ModuleMember, TypeRef {
11911205
* It is true that `getQualifiedName(0) = "foo"` and `getQualifiedName(1) = "bar"`.
11921206
*/
11931207
string getQualifiedName(int i) {
1194-
result = imp.getChild(0).(QL::ImportModuleExpr).getChild().getName(i).getValue()
1208+
result = imp.getChild(0).(QL::ImportModuleExpr).getQualName(i).getValue()
11951209
}
11961210

11971211
/**
1198-
* Gets the full string specifying the module being imported.
1212+
* Gets a full string specifying the module being imported.
1213+
*
1214+
* Does NOT include type arguments!
11991215
*/
12001216
string getImportString() {
1201-
exists(string selec |
1202-
not exists(this.getSelectionName(_)) and selec = ""
1217+
exists(string qual |
1218+
not exists(this.getQualifiedName(_)) and qual = ""
12031219
or
1204-
selec =
1205-
"::" + strictconcat(int i, string q | q = this.getSelectionName(i) | q, "::" order by i)
1220+
qual = strictconcat(int i, string q | q = this.getQualifiedName(i) | q, "." order by i) + "."
12061221
|
1207-
result =
1208-
strictconcat(int i, string q | q = this.getQualifiedName(i) | q, "." order by i) + selec
1222+
result = qual + this.getSelectionName()
12091223
)
12101224
}
12111225

1212-
override Type getResolvedType() {
1213-
exists(FileOrModule mod | resolve(this, mod) | result = mod.toType())
1214-
}
1226+
override Type getResolvedType() { result = this.getModuleExpr().getResolvedType() }
12151227
}
12161228

12171229
/** A formula, such as `x = 6 and y < 5`. */
@@ -2286,6 +2298,19 @@ class ModuleExpr extends TModuleExpr, TypeRef {
22862298
SignatureExpr getArgument(int i) {
22872299
result.toQL() = me.getAFieldOrChild().(QL::ModuleInstantiation).getChild(i)
22882300
}
2301+
2302+
/**
2303+
* Gets the qualified name for this module expression, which does not include the type arguments.
2304+
*/
2305+
string getQualifiedName() {
2306+
exists(string qual |
2307+
not exists(this.getQualifier()) and qual = ""
2308+
or
2309+
qual = this.getQualifier().getQualifiedName() + "::"
2310+
|
2311+
result = qual + this.getName()
2312+
)
2313+
}
22892314
}
22902315

22912316
/** A signature expression, either a `PredicateExpr` or a `TypeExpr`. */

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

Lines changed: 74 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class Module_ extends FileOrModule, TModule {
120120
}
121121
}
122122

123-
private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) {
123+
private predicate resolveImportQualifier(Import imp, ContainerOrModule m, int i) {
124124
not m = TFile(any(File f | f.getExtension() = "ql")) and
125125
exists(string q | q = imp.getQualifiedName(i) |
126126
i = 0 and
@@ -139,7 +139,6 @@ private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) {
139139
m = TFolder(c)
140140
)
141141
or
142-
q = imp.getQualifiedName(i) and
143142
exists(ContainerOrModule container | container = getEnclosingModule(imp).getEnclosing+() |
144143
definesModule(container, q, m, _) and
145144
(
@@ -154,25 +153,18 @@ private predicate resolveQualifiedName(Import imp, ContainerOrModule m, int i) {
154153
)
155154
or
156155
exists(Folder_ mid |
157-
resolveQualifiedName(imp, mid, i - 1) and
156+
resolveImportQualifier(imp, mid, i - 1) and
158157
m.getEnclosing() = mid and
159158
q = m.getName()
160159
)
161160
)
162161
}
163162

164-
private predicate resolveSelectionName(Import imp, ContainerOrModule m, int i) {
163+
private predicate resolveImportQualifier(Import imp, ContainerOrModule m) {
165164
(m.(File_).getFile().getExtension() = "qll" or not m instanceof File_) and
166165
exists(int last |
167-
resolveQualifiedName(imp, m, last) and
168-
last = count(int j | exists(imp.getQualifiedName(j))) - 1
169-
) and
170-
not m instanceof Folder_ and
171-
i = -1
172-
or
173-
exists(ContainerOrModule mid |
174-
resolveSelectionName(imp, mid, i - 1) and
175-
definesModule(mid, imp.getSelectionName(i), m, true)
166+
last = max(int j | exists(imp.getQualifiedName(j))) and
167+
resolveImportQualifier(imp, m, last)
176168
)
177169
}
178170

@@ -202,18 +194,10 @@ private module Cached {
202194
TModule(Module m)
203195
}
204196

205-
/** Holds if import statement `imp` resolves to `m`. */
206-
cached
207-
predicate resolve(Import imp, FileOrModule m) {
208-
exists(int last |
209-
resolveSelectionName(imp, m, last) and
210-
last = count(int j | exists(imp.getSelectionName(j))) - 1
211-
)
212-
}
213-
214197
/** Holds if module expression `me` resolves to `m`. */
215198
cached
216199
predicate resolveModuleRef(TypeRef me, FileOrModule m) {
200+
// base case, resolving a typeref without a qualifier (only moduleexpr can have qualifiers)
217201
not m = TFile(any(File f | f.getExtension() = "ql")) and
218202
not exists(me.(ModuleExpr).getQualifier()) and
219203
exists(ContainerOrModule enclosing, string name | resolveModuleRefHelper(me, enclosing, name) |
@@ -234,17 +218,69 @@ private module Cached {
234218
)
235219
)
236220
or
221+
// recursive case, resolving the qualifier.
237222
exists(FileOrModule mid |
238223
resolveModuleRef(me.(ModuleExpr).getQualifier(), mid) and
239224
definesModule(mid, me.(ModuleExpr).getName(), m, true)
240225
)
241226
}
242227

228+
/**
229+
* Gets the module that the lookup for `ref` should start at.
230+
* For most type references this will simply be the enclosing module.
231+
*
232+
* However, for module expressions inside imports, this will be determined
233+
* by the qualified name of the import (everything before the module expression).
234+
*
235+
* E.g. for an import `import foo.Bar::Baz`, the qualified name of the import is "foo",
236+
* and the module expression is "Bar::Baz".
237+
*/
238+
private ContainerOrModule getStartModule(TypeRef ref) {
239+
if isInsideImport(ref)
240+
then
241+
exists(Import i | ref = i.getModuleExpr().getQualifier*() |
242+
resolveImportQualifier(i, result)
243+
or
244+
not exists(i.getQualifiedName(_)) and
245+
(
246+
result = getEnclosingModule(ref)
247+
or
248+
exists(YAML::QLPack pack |
249+
pack.getAFileInPack() = ref.getLocation().getFile() and
250+
result = TFolder(pack.getADependency*().getFile().getParentContainer())
251+
)
252+
)
253+
)
254+
else result = getEnclosingModule(ref)
255+
}
256+
257+
/** Holds of `me` is part of an import statement. */
258+
pragma[noinline]
259+
private predicate isInsideImport(ModuleExpr me) {
260+
me = any(Import i).getModuleExpr().getQualifier*()
261+
}
262+
263+
/** Gets the enclosing module/container, but stops after the first folder (so no folder -> folder step). */
264+
private ContainerOrModule getEnclosingModuleNoFolderStep(ContainerOrModule m) {
265+
result = m.getEnclosing() and
266+
not (
267+
result instanceof Folder_ and
268+
m instanceof Folder_
269+
)
270+
}
271+
243272
pragma[noinline]
244273
private predicate resolveModuleRefHelper(TypeRef me, ContainerOrModule enclosing, string name) {
245-
enclosing = getEnclosingModule(me).getEnclosing*() and
274+
// The scope is all enclosing modules, the immidiatly containing folder, not the parent folders.
275+
enclosing = getEnclosingModuleNoFolderStep*(getStartModule(me)) and
246276
name = [me.(ModuleExpr).getName(), me.(TypeExpr).getClassName()] and
247-
(not me instanceof ModuleExpr or not enclosing instanceof Folder_) // module expressions are not imports, so they can't resolve to a file (which is contained in a folder).
277+
not exists(me.(ModuleExpr).getQualifier()) and
278+
(
279+
// module expressions are not imports, so they can't resolve to a file (which is contained in a folder).
280+
(not me instanceof ModuleExpr or not enclosing instanceof Folder_)
281+
or
282+
isInsideImport(me) // unless it actually is an import.
283+
)
248284
}
249285
}
250286

@@ -296,7 +332,7 @@ private predicate definesModule(
296332
// import X
297333
exists(Import imp, ContainerOrModule m0 |
298334
container = getEnclosingModule(imp) and
299-
resolve(imp, m0) and
335+
resolveModuleRef(imp.getModuleExpr(), m0) and
300336
not exists(imp.importedAs()) and
301337
definesModule(m0, name, m, true) and
302338
public = getPublicBool(imp)
@@ -306,7 +342,7 @@ private predicate definesModule(
306342
exists(Import imp |
307343
container = getEnclosingModule(imp) and
308344
name = imp.importedAs() and
309-
resolve(imp, m) and
345+
resolveModuleRef(imp.getModuleExpr(), m) and
310346
public = getPublicBool(imp)
311347
)
312348
or
@@ -320,24 +356,6 @@ private predicate definesModule(
320356
}
321357

322358
module ModConsistency {
323-
query predicate noResolve(Import imp) {
324-
not resolve(imp, _) and
325-
not imp.getLocation()
326-
.getFile()
327-
.getAbsolutePath()
328-
.regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*")
329-
}
330-
331-
query predicate multipleResolve(Import imp, int c, ContainerOrModule m) {
332-
c = strictcount(ContainerOrModule m0 | resolve(imp, m0)) and
333-
c > 1 and
334-
resolve(imp, m) and
335-
not imp.getLocation()
336-
.getFile()
337-
.getAbsolutePath()
338-
.regexpMatch(".*/(test|examples|ql-training|recorded-call-graph-metrics)/.*")
339-
}
340-
341359
// This can happen with parameterized modules.
342360
/*
343361
* query predicate multipleResolveModuleRef(ModuleExpr me, int c, ContainerOrModule m) {
@@ -362,4 +380,16 @@ module ModConsistency {
362380
mod instanceof ModuleExpr and
363381
count(mod.(ModuleExpr).getName()) >= 2
364382
}
383+
384+
query predicate uniqueResolve(Import i) {
385+
count(FileOrModule mod |
386+
mod = i.getResolvedModule() and
387+
// don't count the alias reference, only the resolved.
388+
not exists(mod.asModule().getAlias())
389+
) >= 2 and
390+
// paramerized modules are not treated nicely, so we ignore them here.
391+
not i.getResolvedModule().getEnclosing*().asModule().hasParameter(_, _, _)
392+
}
393+
394+
query predicate noResolve(Import i) { not exists(i.getResolvedModule()) }
365395
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ private import Builtins
33
private import codeql_ql.ast.internal.Module
44
private import codeql_ql.ast.internal.AstNodes
55

6+
pragma[nomagic]
67
private predicate definesPredicate(
78
FileOrModule m, string name, int arity, Predicate p, boolean public
89
) {

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

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -602,15 +602,15 @@ module QL {
602602
/** Gets the name of the primary QL class for this element. */
603603
final override string getAPrimaryQlClass() { result = "ImportModuleExpr" }
604604

605-
/** Gets the node corresponding to the field `name`. */
606-
final SimpleId getName(int i) { ql_import_module_expr_name(this, i, result) }
605+
/** Gets the node corresponding to the field `qualName`. */
606+
final SimpleId getQualName(int i) { ql_import_module_expr_qual_name(this, i, result) }
607607

608608
/** Gets the child of this node. */
609-
final QualModuleExpr getChild() { ql_import_module_expr_def(this, result) }
609+
final ModuleExpr getChild() { ql_import_module_expr_def(this, result) }
610610

611611
/** Gets a field or child node of this node. */
612612
final override AstNode getAFieldOrChild() {
613-
ql_import_module_expr_name(this, _, result) or ql_import_module_expr_def(this, result)
613+
ql_import_module_expr_qual_name(this, _, result) or ql_import_module_expr_def(this, result)
614614
}
615615
}
616616

@@ -956,18 +956,6 @@ module QL {
956956
final override string getAPrimaryQlClass() { result = "Qldoc" }
957957
}
958958

959-
/** A class representing `qualModuleExpr` nodes. */
960-
class QualModuleExpr extends @ql_qual_module_expr, AstNode {
961-
/** Gets the name of the primary QL class for this element. */
962-
final override string getAPrimaryQlClass() { result = "QualModuleExpr" }
963-
964-
/** Gets the node corresponding to the field `name`. */
965-
final SimpleId getName(int i) { ql_qual_module_expr_name(this, i, result) }
966-
967-
/** Gets a field or child node of this node. */
968-
final override AstNode getAFieldOrChild() { ql_qual_module_expr_name(this, _, result) }
969-
}
970-
971959
/** A class representing `qualifiedRhs` nodes. */
972960
class QualifiedRhs extends @ql_qualified_rhs, AstNode {
973961
/** Gets the name of the primary QL class for this element. */

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,12 @@ private predicate defines(FileOrModule m, string name, Type t, boolean public) {
327327
public = getPublicBool(ty.getParent())
328328
)
329329
or
330+
exists(Module mod | t = TModule(mod) |
331+
getEnclosingModule(mod) = m and
332+
mod.getName() = name and
333+
public = getPublicBool(mod)
334+
)
335+
or
330336
exists(Class ty | t = TUnion(ty) |
331337
getEnclosingModule(ty) = m and
332338
ty.getName() = name and

ql/ql/src/codeql_ql/style/DeadCodeQuery.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ private AstNode aliveStep(AstNode prev) {
8383
//
8484
// The recursive cases.
8585
//
86+
result = prev.(Import).getModuleExpr()
87+
or
8688
result.getEnclosingPredicate() = prev
8789
or
8890
result = prev.(Call).getTarget()

ql/ql/src/codeql_ql/style/RedundantImportQuery.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ predicate importsFromSameFolder(Import a, Import b) {
3535
predicate problem(Import imp, Import redundant, string message) {
3636
not exists(imp.importedAs()) and
3737
not exists(redundant.importedAs()) and
38+
not exists(imp.getModuleExpr().getQualifier*().getArgument(_)) and // any type-arguments, and we ignore, they might be different.
3839
// skip the top-level language files, they have redundant imports, and that's fine.
3940
not exists(imp.getLocation().getFile().getParentContainer().getFile("qlpack.yml")) and
4041
// skip the DataFlowImpl.qll and similar, they have redundant imports in some copies.

0 commit comments

Comments
 (0)