名称解析¶
QL 编译器将名称解析为程序元素。
与其他编程语言一样,QL 代码中使用的名称与其引用的底层 QL 实体之间存在区别。
QL 中的不同实体可能具有相同的名称,例如,如果它们在不同的模块中定义。因此,QL 编译器能够将名称解析为正确的实体非常重要。
在编写自己的 QL 时,可以使用不同类型的表达式来引用实体。然后,这些表达式将在适当的命名空间中解析为 QL 实体。
- 总之,表达式类型有
名称¶
要解析简单的名称(带元数),编译器会在当前模块的命名空间中查找该名称(以及元数)。
在import 语句中,名称解析稍微复杂一些。例如,假设你定义了一个查询模块 Example.ql
,它包含以下 import 语句
import javascript
编译器首先使用下面描述的限定引用的步骤,查找一个名为javascript.qll
的库模块。如果失败,则检查Example.ql
的模块命名空间中是否定义了名为javascript
的显式模块。
限定引用¶
限定引用是一种模块表达式,它使用.
作为文件路径分隔符。你只能在import 语句中使用这种表达式,来导入由相对路径定义的库模块。
例如,假设你定义了一个查询模块 Example.ql
,它包含以下 import 语句
import examples.security.MyLibrary
为了找到这个库模块的准确位置,QL 编译器将对 import 语句进行如下处理
- 限定引用中的
.
对应于文件路径分隔符,因此它首先从包含Example.ql
的目录中查找examples/security/MyLibrary.qll
。- 如果失败,则相对于查询目录(如果有)查找
examples/security/MyLibrary.qll
。查询目录是第一个包含名为qlpack.yml
的文件的封闭目录。(或者,在旧产品中,是一个名为queries.xml
的文件。)- 如果编译器无法使用上述两种检查找到库文件,则相对于每个库路径条目查找
examples/security/MyLibrary.qll
。库路径通常使用qlpack.yml
文件的libraryPathDependencies
来指定,不过它也可能取决于你用来运行查询的工具,以及你是否指定了任何额外的设置。有关更多信息,请参阅 QL 语言规范中的“库路径”。
如果编译器无法解析 import 语句,则会给出编译错误。
选择¶
可以使用选择来引用特定模块内的模块、类型或谓词。选择的形式为
<module_expression>::<name>
编译器首先解析模块表达式,然后在该模块的命名空间中查找名称。
示例¶
考虑以下库模块
CountriesLib.qll
class Countries extends string {
Countries() {
this = "Belgium"
or
this = "France"
or
this = "India"
}
}
module M {
class EuropeanCountries extends Countries {
EuropeanCountries() {
this = "Belgium"
or
this = "France"
}
}
}
可以编写一个查询,导入CountriesLib
,然后使用M::EuropeanCountries
来引用EuropeanCountries
类
import CountriesLib
from M::EuropeanCountries ec
select ec
或者,可以使用 import 语句中的选择CountriesLib::M
直接导入M
的内容
import CountriesLib::M
from EuropeanCountries ec
select ec
这使得查询可以访问M
中的所有内容,但不能访问CountriesLib
中不在M
中的任何内容。
命名空间¶
在编写 QL 时,了解命名空间(也称为环境)的工作原理非常有用。
与许多其他编程语言一样,命名空间是从**键**到**实体**的映射。键是一种标识符,例如名称,而 QL 实体是模块、类型或谓词。
QL 中的每个模块都有六个命名空间
- **模块命名空间**,其中键是模块名称,实体是模块。
- **类型命名空间**,其中键是类型名称,实体是类型。
- **谓词命名空间**,其中键是谓词名称和元数对,实体是谓词。
- **模块签名命名空间**,其中键是模块签名名称,实体是模块签名。
- **类型签名命名空间**,其中键是类型签名名称,实体是类型签名。
- **谓词签名命名空间**,其中键是谓词签名名称和元数对,实体是谓词签名。
任何模块的六个命名空间并不完全独立
- **模块命名空间**和**模块签名命名空间**之间不能共享键。
- **类型命名空间**和**类型签名命名空间**之间不能共享键。
- **模块命名空间**和**类型签名命名空间**之间不能共享键。
- **类型命名空间**和**模块签名命名空间**之间不能共享键。
- **谓词命名空间**和**谓词签名命名空间**之间不能共享键。
- **模块签名命名空间**和**类型签名命名空间**之间不能共享键。
不同模块的命名空间中的名称之间没有关系。例如,两个不同的模块可以定义谓词getLocation()
,而不会产生混淆。只要命名空间清晰,QL 编译器就会将名称解析为正确的谓词。
全局命名空间¶
包含所有内置实体的命名空间称为**全局命名空间**,并且在任何模块中都可以自动使用。特别地
实际上,这意味着可以在 QL 模块中直接使用内置类型和谓词(无需导入任何库)。也可以直接使用任何数据库谓词和类型——这些取决于你要查询的底层数据库。
本地命名空间¶
除了全局模块、类型和谓词命名空间之外,每个模块还定义了许多本地模块、类型和谓词命名空间。
对于模块M
,区分其**私有声明**、**公开声明**、**导出**和**可见**命名空间非常有用。(这些是泛型描述,但请记住,对于模块、模块签名、类型、类型签名、谓词和谓词签名,始终有一个。)
- 的**私有声明**命名空间
M
包含在中声明的(即定义的)所有实体和别名M
,并被注释为private
。 - 模块
M
的**公开声明**命名空间包含在M
中声明(即定义)且未被标注为private
的所有实体和别名。 - 模块
M
的**导出**命名空间包含 - 模块
M
的**公开声明**命名空间中的所有条目,以及 - 对于每个通过未被标注为
private
的导入语句导入到M
中的模块N
:模块N
的**导出**命名空间中所有不与模块M
的**公开声明**命名空间中的任何条目同名的条目,以及 - 对于每个由
M
实现的模块签名S
:S
中所有不与模块M
的**公开声明**谓词命名空间中的任何条目同名且具有相同元数的模块签名默认谓词的条目。
- 模块
- 模块
- 模块
M
的**可见**命名空间包含 - 模块
M
的**导出**命名空间中的所有条目,以及 - 全局命名空间中的所有条目,以及
- 模块
M
的**私有声明**命名空间中的所有条目,以及 - 对于每个通过被标注为
private
的导入语句导入到M
中的模块N
:模块N
的**导出**命名空间中所有不与模块M
的**公开声明**命名空间中的任何条目同名的条目。 - 如果
M
嵌套在模块N
中:模块N
的**可见**命名空间中所有不与模块M
的**公开声明**命名空间中的任何条目同名的条目,以及 - 模块
M
的所有参数。
- 模块
- 模块
这在示例中更容易理解
OneTwoThreeLib.qll
import MyFavoriteNumbers
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}
private module P {
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
}
}
模块 OneTwoThreeLib
**公开声明**类 OneTwoThree
并**私有声明**模块 P
。
它**导出**类 OneTwoThree
以及由 MyFavoriteNumbers
导出的任何内容(假设 MyFavoriteNumbers
不导出类型 OneTwoThree
,否则该类型将不会被 OneTwoThreeLib
**导出**)。
在其中,类 OneTwoThree
和模块 P
是**可见**的,以及由 MyFavoriteNumbers` 导出的任何内容(假设 MyFavoriteNumbers
不导出类型 OneTwoThree
,否则该类型将不会在 OneTwoThreeLib
中 **可见**)。
示例¶
让我们看看模块、类型和谓词命名空间在具体示例中的样子
例如,你可以定义一个库模块 Villagers
,其中包含在 QL 教程 中定义的一些类和谓词。
Villagers.qll
import tutorial
predicate isBald(Person p) {
not exists(string c | p.getHairColor() = c)
}
class Child extends Person {
Child() {
this.getAge() < 10
}
}
module S {
predicate isSouthern(Person p) {
p.getLocation() = "south"
}
class Southerner extends Person {
Southerner() {
isSouthern(this)
}
}
}
模块命名空间
- 模块
Villagers
的模块命名空间包含以下条目的入口: - 模块
S
。 - 由
tutorial
导出的任何模块。
- 模块
模块 S
的模块命名空间也包含以下条目的入口:模块 S
本身,以及由 tutorial
导出的任何模块。
类型命名空间
- 模块
Villagers
的类型命名空间包含以下条目的入口: - 类
Child
。 - 由模块
tutorial
导出的类型。 - 内置类型,即
int
、float
、string
、date
和boolean
。
- 类
- 模块
S
的类型命名空间包含以下条目的入口: - 所有上述类型。
- 类
Southerner
。
谓词命名空间
- 模块
Villagers
的谓词命名空间包含以下条目的入口: - 谓词
isBald
,元数为 1。 - 由
tutorial
导出的任何谓词(及其元数)。 - 内置谓词。
- 谓词
- 模块
S
的谓词命名空间包含以下条目的入口: - 所有上述谓词。
- 谓词
isSouthern
,元数为 1。