CodeQL 文档

抓捕纵火犯

了解 QL 谓词和类,解决你的第二个 QL 探案谜题。

就在你成功找到小偷并将金冠归还城堡后,又发生了一起可怕的罪行。清晨,一些人在村庄北面的田野里放火,烧毁了所有的庄稼!

你现在被誉为 QL 侦探专家,所以你再次被要求找到罪魁祸首。

这次你有一些额外的信息。村庄的北部和南部之间存在着激烈的竞争,你知道罪犯住在南部。

阅读下面的示例以了解如何在 QL 中定义谓词和类。这些可以使你的查询逻辑更容易理解,并有助于简化你的侦探工作。

选择南部人

这次你只需要考虑特定的一组村民,即住在村庄南部的人。与其在所有查询中都写 getLocation() = "south",不如定义一个新的 谓词 isSouthern

predicate isSouthern(Person p) {
  p.getLocation() = "south"
}

谓词 isSouthern(p) 接受一个参数 p,并检查 p 是否满足属性 p.getLocation() = "south"

注意

  • 谓词的名称总是以小写字母开头。
  • 你还可以定义带有结果的谓词。在这种情况下,关键字 predicate 被替换为结果的类型。这就像引入一个新的参数,特殊的变量 result。例如,int getAge() { result = ... } 返回一个 int

你现在可以使用以下方法列出所有南部人

/* define predicate `isSouthern` as above */

from Person p
where isSouthern(p)
select p

这已经是简化逻辑的一种不错的方式,但我们可以更有效率。目前,查询会查看每一个 Person p,然后限制为满足 isSouthern(p) 的那些。相反,我们可以定义一个新的 Southerner,其中包含我们想要考虑的准确人员。

class Southerner extends Person {
  Southerner() { isSouthern(this) }
}

QL 中的类表示一个逻辑属性:当一个值满足该属性时,它就属于该类。这意味着一个值可以属于多个类——属于某个特定类并不妨碍它也属于其他类。

表达式 isSouthern(this) 定义了类表示的逻辑属性,称为其特征谓词。它使用一个特殊的变量 this,并表示一个 Personthis” 是一个 Southerner,如果属性 isSouthern(this) 成立。

注意

如果你熟悉面向对象的编程语言,你可能倾向于将特征谓词视为构造函数。然而,事实并非如此——它是一个逻辑属性,不会创建任何对象。

你始终需要在 QL 中根据现有(较大的)类来定义类。在我们的示例中,Southerner 是一种特殊的 Person,所以我们说 Southerner扩展(“是…的子集”)Person

使用此类,你现在可以简单地列出所有住在南部的人

from Southerner s
select s

你可能已经注意到,有些谓词是附加的,例如 p.getAge(),而有些则不是,例如 isSouthern(p)。这是因为 getAge() 是一个成员谓词,即仅适用于类成员的谓词。你在类内部定义这样的成员谓词。在本例中,getAge() 定义在类 Person 内。相反,isSouthern 是单独定义的,不在任何类内部。成员谓词特别有用,因为你可以轻松地将它们链接在一起。例如,p.getAge().sqrt() 首先获取 p 的年龄,然后计算该数字的平方根。

旅行限制

另一个你要考虑的因素是,在金冠被盗后实施的旅行限制。最初,村民在村庄内旅行没有限制。因此,谓词 isAllowedIn(string region) 对所有人员和所有地区都成立。以下查询列出了所有村民,因为他们都可以去北部旅行

from Person p
where p.isAllowedIn("north")
select p

然而,在最近的盗窃事件发生后,村民们对潜伏在村庄周围的罪犯更加焦虑,他们不再允许 10 岁以下的儿童离开自己的居住区。

这意味着 isAllowedIn(string region) 不再对所有人员和所有地区都成立,所以如果你要临时覆盖原始谓词,那么 p 必须是儿童。

首先定义一个类 Child,其中包含所有 10 岁以下的村民。然后,你可以将 isAllowedIn(string region) 重新定义为 Child 的成员谓词,以允许儿童仅在自己的区域内移动。这由 region = this.getLocation() 表示。

class Child extends Person {
  /* the characteristic predicate */
  Child() { this.getAge() < 10 }

  /* a member predicate */
  override predicate isAllowedIn(string region) {
    region = this.getLocation()
  }
}

现在尝试将 isAllowedIn(string region) 应用于一个人 p。如果 p 不是儿童,则使用原始定义,但如果 p 是儿童,则新的谓词定义会覆盖原始定义。

你知道纵火犯住在南部,并且他们必须能够去北部旅行。编写一个查询以找到可能的嫌疑人。你也可以扩展 select 子句以列出嫌疑人的年龄。这样你就可以清楚地看到所有儿童都被排除在列表之外。

查看你的答案

你现在可以继续收集更多线索,并找出你怀疑的哪个人放了火……

识别光头强盗

你询问北方人是否还有关于纵火犯的更多信息。幸运的是,你有一位目击者!住在田野旁边的农民看到放火后有两个人逃跑。他只看到了他们的头顶,并注意到他们都是光头。

这是一个非常有用的线索。请记住,你编写了一个 QL 查询来选择所有光头的人

from Person p
where not exists (string c | p.getHairColor() = c)
select p

为了避免每次想要选择一个光头的人时都键入 not exists (string c | p.getHairColor() = c),你可以改为定义另一个新的谓词 isBald

predicate isBald(Person p) {
  not exists (string c | p.getHairColor() = c)
}

属性 isBald(p) 只要 p 是光头就成立,因此你可以用以下内容替换之前的查询

from Person p
where isBald(p)
select p

谓词 isBald 被定义为接受一个 Person,因此它也可以接受一个 Southerner,因为 SouthernerPerson 的子类型。例如,它不能接受一个 int——这会导致错误。

你现在可以编写一个查询来选择可以进入北方的光头南部人。

查看你的答案

你找到了两个纵火犯!他们被逮捕了,村民们再次对你的工作印象深刻。

进一步阅读


答案

在这些答案中,我们使用 /**/ 来标记查询的不同部分。任何位于 /**/ 之间的文本都不会被评估为 QL 代码的一部分,而是被视为注释

练习 1

import tutorial

predicate isSouthern(Person p) { p.getLocation() = "south" }

class Southerner extends Person {
  /* the characteristic predicate */
  Southerner() { isSouthern(this) }
}

class Child extends Person {
  /* the characteristic predicate */
  Child() { this.getAge() < 10 }

  /* a member predicate */
  override predicate isAllowedIn(string region) { region = this.getLocation() }
}

from Southerner s
where s.isAllowedIn("north")
select s, s.getAge()

练习 2

import tutorial

predicate isSouthern(Person p) { p.getLocation() = "south" }

class Southerner extends Person {
  /* the characteristic predicate */
  Southerner() { isSouthern(this) }
}

class Child extends Person {
  /* the characteristic predicate */
  Child() { this.getAge() < 10 }

  /* a member predicate */
  override predicate isAllowedIn(string region) { region = this.getLocation() }
}

predicate isBald(Person p) { not exists(string c | p.getHairColor() = c) }

from Southerner s
where s.isAllowedIn("north") and isBald(s)
select s
  • ©GitHub, Inc.
  • 条款
  • 隐私