CodeQL 文档

名称解析

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 语句进行如下处理

  1. 限定引用中的.对应于文件路径分隔符,因此它首先从包含Example.ql的目录中查找examples/security/MyLibrary.qll
  2. 如果失败,则相对于查询目录(如果有)查找examples/security/MyLibrary.qll。查询目录是第一个包含名为qlpack.yml的文件的封闭目录。(或者,在旧产品中,是一个名为queries.xml的文件。)
  3. 如果编译器无法使用上述两种检查找到库文件,则相对于每个库路径条目查找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 编译器就会将名称解析为正确的谓词。

全局命名空间

包含所有内置实体的命名空间称为**全局命名空间**,并且在任何模块中都可以自动使用。特别地

  • **全局模块命名空间**只有一个条目QlBuiltins
  • **全局类型命名空间**包含原始类型 intfloatstringbooleandate的条目,以及在数据库模式中定义的任何数据库类型
  • **全局谓词命名空间**包含所有内置谓词,以及任何数据库谓词
  • **全局签名命名空间**为空。

实际上,这意味着可以在 QL 模块中直接使用内置类型和谓词(无需导入任何库)。也可以直接使用任何数据库谓词和类型——这些取决于你要查询的底层数据库。

本地命名空间

除了全局模块、类型和谓词命名空间之外,每个模块还定义了许多本地模块、类型和谓词命名空间。

对于模块M,区分其**私有声明**、**公开声明**、**导出**和**可见**命名空间非常有用。(这些是泛型描述,但请记住,对于模块、模块签名、类型、类型签名、谓词和谓词签名,始终有一个。)

  • 的**私有声明**命名空间M包含在中声明的(即定义的)所有实体和别名M,并被注释为private
  • 模块 M 的**公开声明**命名空间包含在 M 中声明(即定义)且未被标注为 private 的所有实体和别名。
  • 模块 M 的**导出**命名空间包含
    1. 模块 M 的**公开声明**命名空间中的所有条目,以及
    2. 对于每个通过未被标注为 private 的导入语句导入到 M 中的模块 N:模块 N 的**导出**命名空间中所有不与模块 M 的**公开声明**命名空间中的任何条目同名的条目,以及
    3. 对于每个由 M 实现的模块签名 SS 中所有不与模块 M 的**公开声明**谓词命名空间中的任何条目同名且具有相同元数的模块签名默认谓词的条目。
  • 模块 M 的**可见**命名空间包含
    1. 模块 M 的**导出**命名空间中的所有条目,以及
    2. 全局命名空间中的所有条目,以及
    3. 模块 M 的**私有声明**命名空间中的所有条目,以及
    4. 对于每个通过被标注为 private 的导入语句导入到 M 中的模块 N:模块 N 的**导出**命名空间中所有不与模块 M 的**公开声明**命名空间中的任何条目同名的条目。
    5. 如果 M 嵌套在模块 N 中:模块 N 的**可见**命名空间中所有不与模块 M 的**公开声明**命名空间中的任何条目同名的条目,以及
    6. 模块 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 导出的类型。
  • 内置类型,即 intfloatstringdateboolean
模块 S 的类型命名空间包含以下条目的入口:
  • 所有上述类型。
  • Southerner

谓词命名空间

模块 Villagers 的谓词命名空间包含以下条目的入口:
  • 谓词 isBald,元数为 1。
  • tutorial 导出的任何谓词(及其元数)。
  • 内置谓词
模块 S 的谓词命名空间包含以下条目的入口:
  • 所有上述谓词。
  • 谓词 isSouthern,元数为 1。
  • ©GitHub, Inc.
  • 条款
  • 隐私