CodeQL 文档

模块

模块提供了一种通过将相关的类型、谓词和其他模块分组在一起来组织 QL 代码的方法。

您可以将模块导入其他文件,这可以避免重复,并有助于将代码结构化为更易于管理的部分。

定义模块

有几种方法可以定义模块——以下是一个最简单方法的示例,声明一个名为 Example显式模块,其中包含一个类 OneTwoThree

module Example {
  class OneTwoThree extends int {
    OneTwoThree() {
      this = 1 or this = 2 or this = 3
    }
  }
}

模块名称可以是任何以大写或小写字母开头的 标识符

.ql.qll 文件也隐式地定义模块。有关更多信息,请参见“模块类型。”

您还可以对模块进行注释。有关更多信息,请参见“注释概述。”

请注意,您只能对 显式模块 进行注释。文件模块不能进行注释。

模块类型

文件模块

每个查询文件(扩展名为 .ql)和库文件(扩展名为 .qll)都隐式地定义一个模块。该模块与文件同名,但文件名中的所有空格都将替换为下划线 (_)。文件的内容构成 模块主体

库模块

库模块由 .qll 文件定义。它可以包含下面 模块主体 中列出的任何元素,除了 select 子句。

例如,请考虑以下 QL 库

OneTwoThreeLib.qll

class OneTwoThree extends int {
  OneTwoThree() {
    this = 1 or this = 2 or this = 3
  }
}

此文件定义了一个名为 OneTwoThreeLib 的库模块。此模块的主体定义了类 OneTwoThree

查询模块

查询模块由 .ql 文件定义。它可以包含下面 模块主体 中列出的任何元素。

查询模块与其他模块略有不同

例如

OneTwoQuery.ql

import OneTwoThreeLib

from OneTwoThree ott
where ott = 1 or ott = 2
select ott

此文件定义了一个名为 OneTwoQuery 的查询模块。此模块的主体包括一个 导入语句 和一个 select 子句

显式模块

您还可以在另一个模块内定义模块。这是一个显式模块定义。

显式模块使用关键字 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> { ... }

依赖类型化的参数与 参数化模块签名 结合使用时特别有用。

模块主体

模块的主体是模块定义中的代码,例如 显式模块 M 中的类 OneTwo

通常,模块的主体可以包含以下结构

导入模块

将代码存储在模块中的主要好处是,您可以在其他模块中重复使用它。要访问外部模块的内容,您可以使用 导入语句 导入模块。

当您导入模块时,这会将它命名空间中的所有名称(私有 名称除外)带入当前模块的 命名空间 中。

导入语句

导入语句用于导入模块。它们的形式为

import <module_expression1> as <name>
import <module_expression2>

导入语句通常列在模块的开头。每个导入语句都导入一个模块。您可以通过包含多个导入语句(每个要导入的模块一个)来导入多个模块。

导入语句也可以用 privatedeprecated 进行 注释。如果一个导入语句用 private 进行注释,则导入的名称不会重新导出。如果导入的名称在给定上下文中只能通过已弃用的导入访问,则在该上下文中使用该名称将生成弃用警告。

您可以使用 as 关键字导入不同名称的模块,例如 import javascript as js

<module_expression> 本身可以是模块名称、选择或限定引用。有关更多信息,请参见“名称解析。”

有关如何查找导入语句的信息,请参见 QL 语言规范中的“模块解析”。

内置模块

QL 定义了一个始终位于作用域中的 QlBuiltins 模块。QlBuiltins 为在 QL 中使用(部分)等价关系 (EquivalenceRelation) 和集合 (InternSets) 定义参数化的子模块。

等价关系

内置的 EquivalenceRelation 模块由类型 TT 上的二元基本关系 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 不将 56 关联到任何节点,因此诱导的关系是 Node 上的部分等价关系,并且也不将 56 关联到任何节点。

以上 select 子句返回以下部分等价关系。

x y
1 1
1 2
2 1
2 2
3 3
3 4
4 3
4 4

集合

内置的 InternSets 模块由 KeyValue 类型以及 Value getAValue(Key key) 关系参数化。 该模块按 KeyValue 列进行分组,并为每个由键关联的值组创建集合。

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

如果两个键 k1k2 与同一组值相关联,那么 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
  • ©GitHub, Inc.
  • 条款
  • 隐私