CodeQL 文档

QL 程序评估

QL 程序通过以下步骤进行评估。

过程

当 QL 程序针对数据库运行时,它会被编译成逻辑编程语言 Datalog 的变体。它针对性能进行了优化,然后进行评估以生成结果。

这些结果是有序元组集。有序元组是有序的值的有限序列。例如,(1, 2, "three") 是一个有序元组,包含两个整数和一个字符串。在程序评估过程中可能会生成中间结果:这些结果也是元组集。

QL 程序从下往上评估,因此谓词通常只在它所依赖的所有谓词都评估之后才进行评估。

数据库包括针对 内置谓词外部谓词 的有序元组集。每次评估都从这些元组集开始。程序中的其余谓词和类型根据它们之间的依赖关系组织成多个层。通过查找每个谓词的最小不动点,对这些层进行评估以生成自己的元组集。(例如,参见“递归”。)

程序的 查询 确定哪些元组集构成程序的最终结果。结果根据查询中的任何排序指令 (order by) 进行排序。

有关评估过程每个步骤的更多详细信息,请参见“QL 语言规范”。

程序的有效性

查询的结果必须始终是有限的值集,否则无法进行评估。如果你的 QL 代码包含无限谓词或查询,QL 编译器通常会给出错误消息,以便你更容易地识别错误。

以下是定义无限谓词的一些常见方法。所有这些都会生成编译错误

  • 以下查询从概念上选择类型为 int 的所有值,而没有限制它们。QL 编译器会返回错误 'i' is not bound to a value

    from int i
    select i
    
  • 以下谓词会生成两个错误:'n' is not bound to a value'result' is not bound to a value

    int timesTwo(int n) {
      result = n * 2
    }
    
  • 以下类 Person 包含所有以 "Peter" 开头的字符串。有无数个这样的字符串,因此这也是一个无效的定义。QL 编译器会给出错误消息 'this' is not bound to a value

    class Person extends string {
      Person() {
        this.matches("Peter%")
      }
    }
    

要修复这些错误,考虑范围限制非常有用:如果谓词或查询的每个变量至少有一个 绑定 出现,则该谓词或查询就是范围限制的。没有绑定出现的变量称为未绑定。因此,要执行范围限制检查,QL 编译器会验证是否存在未绑定的变量。

绑定

为了避免查询中的无限关系,你必须确保没有未绑定变量。为此,可以使用以下机制

  1. 有限类型:有限 类型 的变量是绑定的。特别是,任何不是 基本 的类型都是有限的。要为变量赋予有限类型,你可以 声明 它具有有限类型,使用 强制转换,或者使用 类型检查

  2. 谓词调用:有效的 谓词 通常是范围限制的,因此它会 绑定 所有参数。因此,如果你 调用 变量上的谓词,则该变量会变为绑定状态。

    重要

    如果谓词使用非标准绑定集,则它不会始终绑定所有参数。在这种情况下,谓词调用是否绑定特定参数取决于哪些其他参数被绑定,以及绑定集对该参数的说明。有关更多信息,请参见“绑定集”。

  3. 绑定运算符:大多数运算符(如 算术运算符)要求所有操作数都被绑定。例如,除非你对这两个变量都有一个有限的值集,否则你不能在 QL 中将它们加在一起。

    但是,有些内置运算符可以绑定它们的参数。例如,如果 相等检查(使用 =)的一侧被绑定,而另一侧是一个变量,则该变量也会被绑定。有关示例,请参见下表。

从直观上讲,绑定出现将变量限制为有限的值集,而非绑定出现不会。以下是关于绑定和非绑定变量出现之间差异的一些示例

变量出现 详细信息
x = 1 绑定:将 x 限制为值 1
x != 1, not x = 1 不绑定
x = 2 + 3, x + 1 = 3 绑定
x in [0 .. 3] 绑定
p(x, _) 绑定,因为 p() 是对谓词的调用。
x = y, x = y + 1 当且仅当变量 y 被绑定时,x 被绑定。当且仅当变量 x 被绑定时,y 被绑定。
x = y * 2 如果变量 y 被绑定,则 x 被绑定。y 不被绑定。
x > y xy 不被绑定
"string".matches(x) x 不被绑定
x.matches(y) xy 不被绑定
not (... x ...) 通常,x 不被绑定,因为对绑定出现的否定通常会使其变为非绑定。有一些例外:not not x = 1 被正确地识别为 x 的绑定。
sum(int y | y = 1 and x = y | y) x 不被绑定。 strictsum(int y | y = 1 and x = y | y) 将对 x 进行绑定。如果聚合是严格的,则聚合体内的表达式的绑定范围仅限于聚合体外部。
x = 1 or y = 1 xy 不被绑定。第一个子表达式 x = 1x 进行绑定,第二个子表达式 y = 1y 进行绑定。但是,用 析取 将它们组合起来只对所有析取式都进行绑定的变量进行绑定,在本例中,没有变量。

虽然变量的出现可以是绑定或非绑定,但变量是“绑定”还是“未绑定”的属性是一个全局概念,单个绑定出现就足以使变量绑定。

因此,可以通过提供绑定出现来修复上面的“无限”示例。例如,与其使用 int timesTwo(int n) { result = n * 2 },你可以编写

int timesTwo(int n) {
  n in [0 .. 10] and
  result = n * 2
}

现在,谓词绑定了 n,而变量 result 会通过计算 result = n * 2 自动绑定。

  • ©GitHub, Inc.
  • 条款
  • 隐私