ToB企服应用市场:ToB评测及商务社交产业平台

标题: CodeQL学习笔记(1)-QL语法(逻辑连接词、量词、聚合词、谓词和类) [打印本页]

作者: 魏晓东    时间: 2024-10-25 13:28
标题: CodeQL学习笔记(1)-QL语法(逻辑连接词、量词、聚合词、谓词和类)
近来在学习CodeQL,对于CodeQL就不先容了,现在网上一搜一大把。本系列是学习CodeQL的个人学习笔记,根据个人知识库笔记修改整理而来的,分享出来共同砚习。个人觉得QL的语法比较反人类,至少与现在主流的这些OOP语言相比,还是有一定难度的。与现在网上的大多数所谓CodeQL教程不同,本系列基于官方文档景象实例,包含大量的个人明白、思考和延伸,直入主题,只切关键,几乎没有废话,并且坚持用从每一个实例中学习总结归纳,再到实例中验证。希望能给各位一点不一样的见解和思路。当然,也正是如此必定会包含一定的错误,希望各位大佬能在评论区留言指正。
先来看一下一些基本的概念和结构
  1. // 基本结构
  2. from /* variable declarations */
  3. where /* logical formulas */
  4. select /* expressions */
  5. from int a, int b
  6. where x = 3, y = 4
  7. select x, y
  8. // 找出1-10内的勾股数
  9. from int x, int y, int z
  10. where x in [1..10], y in [1..10], z in [1..10] and
  11.     x * x + y * y = z * z
  12. select x, y, z
  13. // 或下面这种类写法,封装和方法复用
  14. class SmallInt extends int {
  15.     SmallInt(){
  16.         this in [1..10]
  17.     }
  18.     int square(){
  19.         result = this * this
  20.     }
  21. }
  22. from SmallInt x, SmallInt y, SmallInt z
  23. where x.sqrt() + y.square() = z.sqrt()
  24. select x, y, z
复制代码
逻辑连接词、量词、聚合词
  1. from Person p
  2. where p.getAge() = max(int i | exists(Person t | t.getAge() = i ) | i)        // 通用聚合语法,比较冗长
  3. select p
  4. // 或用以下有序聚合方式
  5. select max(Person p | | p order by p.getAge())
复制代码
exists( | )
(  |  |  )
e.g. exists( Person p | p.getName() = "test" ),判断是否存在一个人的名字为test
max(int i | exists(Person p | p.getAge() = i) | i),第二部分的意思是拿到全部人的年龄放入i,第三部分是作用范围为i,现在i为int数组,存放全部人的年龄,即最终计算的是max(i)
select max(Person p | | p order by p.getAge()),考虑每个人,取出年龄最大的人。过程是按照年龄来取最大值,换句话说,order by p.getAge() 是告诉 max() 函数要基于 getAge() 来找最大值,并不涉及对全部对象的排序操作。
  1. // 其他有序聚合练习
  2. select min(Person p | p.getLocation() = "east" | p order by p.getHeight())  // 村东最矮的人
  3. select count(Person p | p.getLocation() = "south" | p)  // 村南的人数
  4. select avg(Person p |  | p.getHeight()) // 村民平均身高
  5. select sum(Person p | p.getHairColor() = "brown" | p.getAge())  // 所有棕色头发的村民年龄总和
  6. // 综合练习,https://codeql.github.com/docs/writing-codeql-queries/find-the-thief/#the-real-investigation
  7. import tutorial
  8. from Person p
  9. where
  10.     p.getHeight() > 150 and // 身高超过150
  11.     not p.getHairColor() = "blond" and  // 头发颜色不是金发
  12.     exists(string c | p.getHairColor() = c) and // 不是秃头。这里表示这个人存在某种发色,但不用具象化
  13.     not p.getAge() < 30 and // 年龄满30岁。也可以是p.getAge() >= 30
  14.     p.getLocation() = "east" and    // 住在东边
  15.     ( p.getHairColor() = "black" or p.getHairColor() = "brown" ) and    // 头发是黑色或棕色
  16.     not (p.getHeight() > 180 and p.getHeight() < 190) and   // 没有(超过180且矮于190)
  17.     exists(Person t | t.getAge() > p.getAge()) and   // 不是最年长的人。这里用存在语法是,存在一个人比他的年龄大
  18.     exists(Person t | t.getHeight() > p.getHeight()) and    // 不是最高的人
  19.     p.getHeight() < avg(Person t | | t.getHeight()) and // 比平均身高要矮。所有人,没有限制范围
  20.     p = max(Person t | t.getLocation() = "east" | t order by t.getAge())    // 东部年纪最大的人。这一行是官方给的参考,但是官方文档中说 "Note that if there are several people with the same maximum age, the query lists all of them.",如果存在最大年龄相同的两个人会同时列出,可能会造成不可控的后果。
  21.     // p.getAge() = max(Person t | t.getLocation() = "east" | t.getAge())   // 按照个人理解和chatgpt的解答,应该使用这种方式
  22. select p
复制代码
谓词和种别

CodeQL中的谓词(Predicates)的说法大概可以明白为其他高级编程语言中的函数,同样拥有可传参、返回值、可复用等特性
先来看一个简单的例子
  1. import tutorial
  2. predicate isSouthern(Person p) {
  3.     p.getLocation() = "south"
  4. }
  5. from Person p
  6. where isSouthern(p)
  7. select p
复制代码
这里的predicate为一个逻辑条件判断,返回true or false,有些类似于boolean,当然ql中有单独的boolean类型,还是有一定区别的,只是明白上可以联系起来明白,这里先不睁开
谓词的定义方式和函数类似,此中的predicate可以替换为返回结果类型,比方int getAge() { result = xxx },谓词名称只能以小写字母开头
此外,还可以定义一个新类,直接包含isSouthern的人
  1. class Southerner extends Person {
  2.   Southerner() { isSouthern(this) }
  3. }
  4. from Southerner s
  5. select s
复制代码
这里类似于面向对象语言(OOL)中的类定义,同样拥有继承、封装、方法等;这里的Southerner()类似于构造函数,但是不同于类中的构造函数,这里是一个逻辑属性,并不会创建对象。ool类中的方法在ql中被称为类成员谓词
表达式isSouthern(this)定义了这个类的逻辑属性,称作特性谓词,他用一个变量this(这里的this明白同ool)表现:如果属性isSouthern(this)成立,则一个Person--this是一个Southerner。简单明白就是ql中每个继承子类的特性谓词表现的是什么样的父类是我这种子类、我这种子类在父类的基础上还具有什么特性/特性
引用官方文档:QL 中的类表现一种逻辑属性:当某个值满足该属性时,它就是该类的成员。这意味着一个值可以属于许多类 — 属于某个特定类并不妨碍它属于其他类。
来看下面这个例子
  1. class Child extends Person {
  2.     Child(){
  3.         this.getAge() < 10
  4.     }
  5.     override predicate isAllowedIn(string region) {
  6.         region = this.getLocation()
  7.     }
  8. }
  9. // Person父类中的isAllowedIn实现如下:
  10. predicate isAllowedIn(string region) { region = ["north", "south", "east", "west"] }
  11. // 父类isAllowedIn(region)方法永远返回的是true,子类返回的是当前所在区域才为true(getLocation()方法)
复制代码
看一个完备的例子
  1. import tutorial
  2. predicate isSoutherner(Person p) {
  3.     p.getLocation() = "south"
  4. }
  5. class Southerner extends Person {
  6.     Southerner(){isSoutherner(this)}
  7. }
  8. class Child extends Person {
  9.     Child(){this.getAge() < 10}
  10.     override predicate isAllowedIn(string region) {
  11.         region = this.getLocation()
  12.     }
  13. }
  14. from Southerner s
  15. where s.isAllowedIn("north")
  16. select s, s.getAge()
复制代码
这里有个概念非常重要,要与ool的类完全区别开来,在ool的类中,继承的子类中重构的方法是不会影响其他继承子类的,每个子类不必要考虑是否交错。但是在QL中,引用官方文档的一句话QL 中的类表现一种逻辑属性:当某个值满足该属性时,它就是该类的成员。这意味着一个值可以属于许多类 — 属于某个特定类并不妨碍它属于其他类,在ql的每个子类中,只要满足其特性谓词,就是这个子类的成员。
针对如上代码中这个详细的例子,如果Person中有人同时满足Southerner和Child的特性关系,则同时属于这两个类,天然也会继承此中的成员谓词。
个人明白,其实QL中的子类就是把父类全部拿出来,然后根据特性谓词来匹配父类中的某些元素,然后去复写/重构此中的这些元素的成员谓词,究竟上是对父类中的元素进行了修改。下面用三个实例来对比明白
  1. // 从所有Person中取出当前在South的,然后取出其中能去north的。因为把child限定了只能呆在当地,因此取出的这部分Southerner中的Child全都没法去north,因此就把这部分(原本在South的)Child过滤了
  2. from Southerner s
  3. where s.isAllowedIn("north")
  4. select s
  5. // 取出所有Child,因此他们都只能呆在原地,因此找谁能去north就是找谁原本呆在north
  6. from Child c
  7. where c.isAllowedIn("north")
  8. select c
  9. // 取出所有Person,要找谁能去north的,即找所有成年人(默认所有人都可以前往所有地区)和找本来就呆在north的Child
  10. from Person p
  11. where p.isAllowedIn("north")
  12. select p
复制代码
延伸一下,如果多个子种别同时重构override了同一个成员谓词,那么遵循如下规则(先假定有三个类A、B、C)(后面有总结):
比方:
  1. class OneTwoThree extends int {
  2.   OneTwoThree() { // 特征谓词
  3.     this = 1 or this = 2 or this = 3
  4.   }
  5.   string getAString() { // 成员谓词
  6.     result = "One, two or three: " + this.toString()
  7.   }
  8. }
  9. class OneTwo extends OneTwoThree {
  10.   OneTwo() {
  11.     this = 1 or this = 2
  12.   }
  13.   override string getAString() {
  14.     result = "One or two: " + this.toString()
  15.   }
  16. }
  17. from OneTwoThree o
  18. select o, o.getAString()
  19. /* result:
  20. o        getAString() result
  21. 1        One or two: 1
  22. 2        One or two: 2
  23. 3        One, two or three: 3
  24. // 理解:onetwothree类定义了1 2 3,onetwo重构了onetwothree中1和2的成员谓词。因此onetwothree o中有3个,其中的1和2使用onetwo的成员谓词,3使用onetwothree的成员谓词
  25. */
复制代码
情况1: 在这个基础上加上另一个种别(重要),A->B, A->C
  1. class TwoThree extends OneTwoThree{
  2.   TwoThree() {
  3.     this = 2 or this = 3
  4.   }
  5.   override string getAString() {
  6.     result = "Two or three: " + this.toString()
  7.   }
  8. }
  9. /*
  10. command:
  11. from OneTwoThree o
  12. select o, o.getAString()
  13. result:
  14. o        getAString() result
  15. 1        One or two: 1
  16. 2        One or two: 2
  17. 2        Two or three: 2
  18. 3        Two or three: 3
  19. // 理解:twothree和onetwo重合了two,但是不像其他ool,ql并不会冲突,而是并存。
  20. ---
  21. command:
  22. from OneTwo o
  23. select o, o.getAString()
  24. result:
  25. 1        One or two: 1
  26. 2        One or two: 2
  27. 2        Two or three: 2
  28. // 理解:twothree和onetwo都重构了其中的2,由于ql不会冲突,所以并存。由于o的类型是onetwo,因此"地基"是1和2,然后再加上twothree重构的2
  29. ---
  30. command:
  31. from TwoThree o
  32. select o, o.getAString()
  33. result:
  34. 2        One or two: 2
  35. 2        Two or three: 2
  36. 3        Two or three: 3
  37. // 理解: twothree和onetwo都重构了2,由于ql不会冲突,会并存。由于o的类型是twothree,所以“地基”是2和3,然后再加上onetwo重构的2
  38. */
复制代码
情况2: A->B->C(继承链)
  1. class Two extends TwoThree {
  2.     Two() {
  3.         this = 2
  4.     }
  5.     override string getAString() {
  6.         result = "Two: " + this.toString()
  7.     }
  8.   }
  9. from TwoThree o
  10. select o, o.getAString()
  11. /* result:
  12. o        getAString() result
  13. 1        One or two: 2
  14. 2        Two: 2
  15. 3        Two or three: 3
  16. // 理解:在上面的例子的基础上,Two重构了twothree中的成员谓词,因此与twothree不是共存关系
  17. */
  18. from OneTwo o
  19. select o, o.getAString()
  20. /* result:
  21. o        getAString() result
  22. 1        One or two: 1
  23. 2        One or two: 2
  24. 3        Two: 2
  25. // 理解:在之前例子的基础上,OneTwo和TwoThree共存,但是Two把TwoThree中的一部分给override了(即Two和TwoThree并不是共存关系)
  26. */
复制代码
阶段总结:根据上面这么多例子的学习,总结归纳起来其实很简单,核心要义就是搞清楚“继承链关系”。如果两个种别是继承的同一个父类,那么他们两个的结果共存;如果两个类是从属关系(父与子),那么子类覆盖父类对应的部分。
比方上面的例子中,OneTwo和TwoThree是并存关系,同时继承OneTwoThree,所以他们的结果共存,不冲突;TwoThree和Two是从属关系,所以根据最子类优先原则,覆盖对应TwoThree中的内容(Two也间接继承OneTwoThree,所以对全部父类包罗OneTwoThree造成影响)。
情况3: 多重继承
  1. class Two extends OneTwo, TwoThree {
  2.     Two() {
  3.         this = 2
  4.     }
  5.     override string getAString() {
  6.         result = "Two: " + this.toString()
  7.     }
  8.   }
  9. // 解释1:Two同时继承TwoThree和OneTwo,如果不写条件谓词,则默认为同时满足两个父类条件,如果写,则范围也要小于等于这个交集范围。
  10. // 解释2:如果多重继承的父类中同一名称的成员谓词有多重定义,则必须覆盖这些定义避免歧义。在这里的Two的getAString()是不能省略的
  11. from OneTwoThree o
  12. select o, o.getAString()
  13. /* result:
  14. o        getAString() result
  15. 1        One or two: 1
  16. 2        Two: 2
  17. 3        Two or three: 3
  18. // 理解:由于two与onetwo和twothree是父子关系,因此直接把共有的two全部覆盖,不是并存关系
  19. */
复制代码
在这个基础上再去创建一个谓词,用于判断是否是秃头isBald
  1. predicate isBald(Person p) {
  2.     not exists(string c | p.getHairColor() = c)    // 不加not表示某人有头发
  3. }
  4. // 获得最终结果,允许进入北方的南方秃头
  5. from Southerner s
  6. where s.isAllowedIn("north") and isBald(s)
  7. select s, s.getAge()
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4