模块¶
模块提供了一种通过将相关的类型、谓词和其他模块分组在一起来组织 QL 代码的方法。
您可以将模块导入其他文件,这可以避免重复,并有助于将代码结构化为更易于管理的部分。
定义模块¶
有几种方法可以定义模块——以下是一个最简单方法的示例,声明一个名为 Example
的 显式模块,其中包含一个类 OneTwoThree
module Example {
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}
}
模块名称可以是任何以大写或小写字母开头的 标识符。
.ql
或 .qll
文件也隐式地定义模块。有关更多信息,请参见“模块类型。”
您还可以对模块进行注释。有关更多信息,请参见“注释概述。”
请注意,您只能对 显式模块 进行注释。文件模块不能进行注释。
模块类型¶
文件模块¶
每个查询文件(扩展名为 .ql
)和库文件(扩展名为 .qll
)都隐式地定义一个模块。该模块与文件同名,但文件名中的所有空格都将替换为下划线 (_
)。文件的内容构成 模块主体。
显式模块¶
您还可以在另一个模块内定义模块。这是一个显式模块定义。
显式模块使用关键字 module
后跟模块名称,然后是包含在花括号中的模块主体来定义。它可以包含下面“模块主体”中列出的任何元素,除了 select 子句。
例如,您可以将以下 QL 代码片段添加到 上面 定义的库文件 OneTwoThreeLib.qll 中
...
module M {
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
}
}
这定义了一个名为 M
的显式模块。此模块的主体定义了类 OneTwo
。
参数化模块¶
参数化模块是 QL 对泛型编程的方法。与显式模块类似,参数化模块使用关键字 module
在其他模块中定义。除了模块名称之外,参数化模块还在名称和模块主体之间声明一个或多个参数。
例如,请考虑模块 M
,它接受两个谓词参数,并定义一个将它们依次应用的新谓词
module M<transformer/1 first, transformer/1 second> {
bindingset[x]
int applyBoth(int x) {
result = second(first(x))
}
}
参数化模块不能直接引用。相反,您通过向模块传递包含在尖括号 (<
和 >
) 中的参数来实例化参数化模块。实例化的参数化模块可以用作 模块表达式,与显式模块引用相同。
例如,我们可以使用两个相同的参数 increment
来实例化 M
,创建一个包含一个谓词的模块,该谓词加 2
bindingset[result] bindingset[x]
int increment(int x) { result = x + 1 }
module IncrementTwice = M<increment/1, increment/1>;
select IncrementTwice::applyBoth(40) // 42
参数化模块的参数使用 签名 进行(元)类型化。
例如,在前面的两个代码片段中,我们依赖于谓词签名 transformer
bindingset[x]
signature int transformer(int x);
参数化模块的实例化是应用性的。也就是说,如果您使用等效参数两次实例化一个参数化模块,则产生的对象是相同的。在此上下文中,如果参数仅在 弱别名 上有所不同,则它们被视为等效。这对于参数化模块中的类型定义特别重要,例如通过 类 或通过 newtype,因为此类类型定义的重复会导致不兼容的类型。
以下示例在对谓词 foo
的调用中两次实例化模块 M
。第一个调用有效,但第二个调用会产生错误。
bindingset[this]
signature class TSig;
module M<TSig T> {
newtype A = B() or C()
}
string foo(M<int>::A a) { ... }
select foo(M<int>::B()), // valid: repeated identical instantiation of M does not duplicate A, B, C
foo(M<float>::B()) // ERROR: M<float>::B is not compatible with M<int>::A
模块参数是依赖类型化的,这意味着参数定义中的签名表达式可以引用前面的参数。
例如,我们可以声明 T2
的签名依赖于 T1
,强制这两个参数之间存在子类型关系
signature class TSig;
module Extends<TSig T> { signature class Type extends T; }
module ParameterizedModule<TSig T1, Extends<T1>::Type T2> { ... }
依赖类型化的参数与 参数化模块签名 结合使用时特别有用。
导入模块¶
将代码存储在模块中的主要好处是,您可以在其他模块中重复使用它。要访问外部模块的内容,您可以使用 导入语句 导入模块。
当您导入模块时,这会将它命名空间中的所有名称(私有 名称除外)带入当前模块的 命名空间 中。
导入语句¶
导入语句用于导入模块。它们的形式为
import <module_expression1> as <name>
import <module_expression2>
导入语句通常列在模块的开头。每个导入语句都导入一个模块。您可以通过包含多个导入语句(每个要导入的模块一个)来导入多个模块。
导入语句也可以用 private
或 deprecated
进行 注释。如果一个导入语句用 private
进行注释,则导入的名称不会重新导出。如果导入的名称在给定上下文中只能通过已弃用的导入访问,则在该上下文中使用该名称将生成弃用警告。
您可以使用 as
关键字导入不同名称的模块,例如 import javascript as js
。
<module_expression>
本身可以是模块名称、选择或限定引用。有关更多信息,请参见“名称解析。”
有关如何查找导入语句的信息,请参见 QL 语言规范中的“模块解析”。
内置模块¶
QL 定义了一个始终位于作用域中的 QlBuiltins
模块。QlBuiltins
为在 QL 中使用(部分)等价关系 (EquivalenceRelation
) 和集合 (InternSets
) 定义参数化的子模块。
等价关系¶
内置的 EquivalenceRelation
模块由类型 T
和 T
上的二元基本关系 base
参数化。 base
的对称和传递闭包在 T
上诱导了一个部分等价关系。 如果 T
的每个值都出现在 base
中,则诱导的关系是 T
上的等价关系。
EquivalenceRelation
模块导出一个 getEquivalenceClass
谓词,该谓词获取与给定 T
元素关联的等价类(如果有的话),由 base
诱导的(部分)等价关系。
以下示例说明了 EquivalenceRelation
模块在生成自定义等价关系中的应用。
class Node extends int {
Node() { this in [1 .. 6] }
}
predicate base(Node x, Node y) {
x = 1 and y = 2
or
x = 3 and y = 4
}
module Equiv = QlBuiltins::EquivalenceRelation<Node, base/2>;
from int x, int y
where Equiv::getEquivalenceClass(x) = Equiv::getEquivalenceClass(y)
select x, y
由于 base
不将 5
或 6
关联到任何节点,因此诱导的关系是 Node
上的部分等价关系,并且也不将 5
或 6
关联到任何节点。
以上 select 子句返回以下部分等价关系。
x | y |
---|---|
1 | 1 |
1 | 2 |
2 | 1 |
2 | 2 |
3 | 3 |
3 | 4 |
4 | 3 |
4 | 4 |
集合¶
内置的 InternSets
模块由 Key
和 Value
类型以及 Value getAValue(Key key)
关系参数化。 该模块按 Key
对 Value
列进行分组,并为每个由键关联的值组创建集合。
InternSets
模块导出一个函数 Set getSet(Key key)
关系,该关系将键与由 getAValue
与给定键相关的值的集合相关联。 集合由导出的 Set
类型表示,该类型公开一个 contains(Value v)
成员谓词,该谓词适用于给定集合中包含的值。 getSet(k).contains(v) 因此等效于 v = getAValue(k),如以下 InternSets
示例所示。
int getAValue(int key) {
key = 1 and result = 1
or
key = 2 and
(result = 1 or result = 2)
or
key = 3 and result = 1
or
key = 4 and result = 2
}
module Sets = QlBuiltins::InternSets<int, int, getAValue/1>;
from int k, int v
where Sets::getSet(k).contains(v)
select k, v
这将计算为 getAValue 关系。
k | v |
---|---|
1 | 1 |
2 | 1 |
2 | 2 |
3 | 1 |
4 | 2 |
如果两个键 k1 和 k2 与同一组值相关联,那么 getSet(k1) = getSet(k2)。 对于上面的示例,键 1 和 3 与同一组值(即包含 1 的单例集)相关联,因此由 getSet
关联到同一组。
from int k1, int k2
where Sets::getSet(k1) = Sets::getSet(k2)
select k1, k2
因此,以上查询计算为。
k1 | k2 |
---|---|
1 | 1 |
1 | 3 |
2 | 2 |
3 | 1 |
3 | 3 |
4 | 4 |