CodeQL 文档

谓词

谓词用于描述构成 QL 程序的逻辑关系。

严格地说,谓词评估为一组元组。例如,考虑以下两个谓词定义

predicate isCountry(string country) {
  country = "Germany"
  or
  country = "Belgium"
  or
  country = "France"
}

predicate hasCapital(string country, string capital) {
  country = "Belgium" and capital = "Brussels"
  or
  country = "Germany" and capital = "Berlin"
  or
  country = "France" and capital = "Paris"
}

谓词 isCountry 是单元组的集合 {("Belgium"),("Germany"),("France")},而 hasCapital 是二元组的集合 {("Belgium","Brussels"),("Germany","Berlin"),("France","Paris")}。这些谓词的元数分别为 1 和 2。

通常,谓词中的所有元组都具有相同数量的元素。谓词的元数是元素的数量,不包括可能的 result 变量。有关更多信息,请参见“带结果的谓词”。

QL 中有许多内置谓词。您可以在任何查询中使用它们,而无需导入任何其他模块。除了这些内置谓词之外,您还可以定义自己的

定义谓词

定义谓词时,您应该指定

  1. 关键字 predicate(对于无结果的谓词),或结果的类型(对于带结果的谓词)。
  2. 谓词的名称。这是一个以小写字母开头的标识符
  3. 谓词的参数(如果有),用逗号分隔。对于每个参数,指定参数类型和参数变量的标识符。
  4. 谓词主体本身。这是一个用大括号括起来的逻辑公式。

注意

一个抽象外部谓词没有主体。要定义这样的谓词,请在谓词定义的末尾加上分号 (;) 代替。

无结果的谓词

这些谓词定义以关键字 predicate 开头。如果值满足主体中的逻辑属性,则谓词对于该值成立。

例如

predicate isSmall(int i) {
  i in [1 .. 9]
}

如果 i 是一个整数,则 isSmall(i) 成立,当且仅当 i 是一个小于 10 的正整数。

带结果的谓词

您可以通过用结果类型替换关键字 predicate 来定义带结果的谓词。这引入了特殊变量 result

例如

int getSuccessor(int i) {
  result = i + 1 and
  i in [1 .. 9]
}

如果 i 是一个小于 10 的正整数,则谓词的结果是 i 的后继。

请注意,您可以像对谓词的任何其他参数一样使用 result。您可以以您喜欢的任何方式表达 result 与其他变量之间的关系。例如,给定一个谓词 getAParentOf(Person x),它返回 x 的父母,您可以定义一个“反向”谓词,如下所示

Person getAChildOf(Person p) {
  p = getAParentOf(result)
}

谓词也可能为其参数的每个值返回多个结果(或根本不返回)。例如

string getANeighbor(string country) {
  country = "France" and result = "Belgium"
  or
  country = "France" and result = "Germany"
  or
  country = "Germany" and result = "Austria"
  or
  country = "Germany" and result = "Belgium"
}
在这种情况下
  • 谓词调用 getANeighbor("Germany") 返回两个结果:"Austria""Belgium"
  • 谓词调用 getANeighbor("Belgium") 不返回任何结果,因为 getANeighbor 没有为 "Belgium" 定义 result

递归谓词

QL 中的谓词可以是递归的。这意味着它直接或间接地依赖于自身。

例如,您可以使用递归来改进上面的示例。就目前而言,在 getANeighbor 中定义的关系不是对称的——它没有捕捉到如果 x 是 y 的邻居,则 y 是 x 的邻居这一事实。捕捉到这一点的一种简单方法是递归调用此谓词,如下所示

string getANeighbor(string country) {
  country = "France" and result = "Belgium"
  or
  country = "France" and result = "Germany"
  or
  country = "Germany" and result = "Austria"
  or
  country = "Germany" and result = "Belgium"
  or
  country = getANeighbor(result)
}

现在 getANeighbor("Belgium") 也返回结果,即 "France""Germany"

有关递归谓词和查询的更一般性讨论,请参见“递归”。

谓词的种类

谓词有三种,分别是:非成员谓词、成员谓词和特征谓词。

非成员谓词是在类之外定义的,也就是说,它们不是任何类的成员。

有关其他谓词种类的更多信息,请参见“”主题中的特征谓词成员谓词

以下是一个示例,展示了每种类型的谓词

int getSuccessor(int i) {  // 1. Non-member predicate
  result = i + 1 and
  i in [1 .. 9]
}

class FavoriteNumbers extends int {
  FavoriteNumbers() {  // 2. Characteristic predicate
    this = 1 or
    this = 4 or
    this = 9
  }

  string getName() {   // 3. Member predicate for the class `FavoriteNumbers`
    this = 1 and result = "one"
    or
    this = 4 and result = "four"
    or
    this = 9 and result = "nine"
  }
}

您也可以为这些谓词中的每一个添加注释。请参阅注释列表,了解每种类型的谓词可用的注释。

绑定行为

必须能够在有限的时间内评估谓词,因此它所描述的集合通常不允许是无限的。换句话说,谓词只能包含有限数量的元组。

当 QL 编译器可以证明谓词包含未限制为有限数量值的变量时,它会报告错误。有关更多信息,请参见“绑定”。

以下是一些无限谓词的示例

/*
  Compilation errors:
  ERROR: "i" is not bound to a value.
  ERROR: "result" is not bound to a value.
  ERROR: expression "i * 4" is not bound to a value.
*/
int multiplyBy4(int i) {
  result = i * 4
}

/*
  Compilation errors:
  ERROR: "str" is not bound to a value.
  ERROR: expression "str.length()" is not bound to a value.
*/
predicate shortString(string str) {
  str.length() < 10
}

multiplyBy4 中,参数 i 被声明为一个 int,它是一个无限类型。它在二元运算符 * 中使用,而该运算符不会绑定其操作数。 result 最初是未绑定的,并且仍然是未绑定的,因为它与 i * 4 进行相等性检查,而 i * 4 也是未绑定的。

shortString 中,str 仍然是未绑定的,因为它使用无限类型 string 声明,并且内置函数 length() 不会绑定它。

绑定集

有时您可能想要定义一个“无限谓词”,因为您只打算在受限的参数集上使用它。在这种情况下,您可以使用 bindingset 注释 来指定一个显式绑定集。此注释适用于任何类型的谓词。

例如

bindingset[i]
int multiplyBy4(int i) {
  result = i * 4
}

from int i
where i in [1 .. 10]
select multiplyBy4(i)

虽然 multiplyBy4 是一个无限谓词,但上面的 QL 查询 是合法的。它首先使用 bindingset 注释来声明,只要 i 绑定到有限数量的值,谓词 multiplyBy4 就会是有限的。然后它在 i 被限制在范围 [1 .. 10] 的上下文中使用该谓词。

也可以为谓词指定多个绑定集。这可以通过添加多个绑定集注释来完成,例如

bindingset[x] bindingset[y]
predicate plusOne(int x, int y) {
  x + 1 = y
}

from int x, int y
where y = 42 and plusOne(x, y)
select x, y
以这种方式指定的多个绑定集是相互独立的。上面的示例意味着
  • 如果 x 被绑定,则 xy 被绑定。
  • 如果 y 被绑定,则 xy 被绑定。

也就是说,bindingset[x] bindingset[y],它声明 xy 中至少有一个必须被绑定,不同于 bindingset[x, y],它声明 xy 都必须被绑定。

后者在您想要声明一个带结果的谓词时可能很有用,该谓词接受多个输入参数。例如,以下谓词接受一个字符串 str 并将其截断为不超过 len 个字符的长度

bindingset[str, len]
string truncate(string str, int len) {
  if str.length() > len
  then result = str.prefix(len)
  else result = str
}

然后您可以在一个select 子句中使用它,例如

select truncate("hello world", 5)

数据库谓词

您查询的每个数据库都包含表示值之间关系的表。这些表(“数据库谓词”)在 QL 中的处理方式与其他谓词相同。

例如,如果一个数据库包含一个 persons 表,你可以编写 persons(x, firstName, _, age) 来约束 xfirstNameage 分别为该表中行的第一、第二和第四列。

唯一的区别是,你无法在 QL 中定义数据库谓词。它们由底层数据库定义。因此,可用的数据库谓词根据你查询的数据库而有所不同。

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