最近在学习CodeQL,对于CodeQL就不介绍了,目前网上一搜一大把。本系列是学习CodeQL的个人学习笔记,根据个人知识库笔记修改整理而来的,分享出来共同学习。个人觉得QL的语法比较反人类,至少与目前主流的这些OOP语言相比,还是有一定难度的。与现在网上的大多数所谓CodeQL教程不同,本系列基于官方文档和情景实例,包含大量的个人理解、思考和延伸,直入主题,只切要害,几乎没有废话,并且坚持用从每一个实例中学习总结归纳,再到实例中验证。希望能给各位一点不一样的见解和思路。当然,也正是如此必定会包含一定的错误,希望各位大佬能在评论区留言指正。
模块
模块类似于"包"的概念,可以把一定的内容进行封装,提供了一种组织QL代码的方法,避免代码重复。
模块一般可以包含如下六大结构:
文件模块
事实上,每一个查询文件(后缀为.ql
)和库文件(后缀.qll
)都定义了一个隐式的模块,这个模块名和文件名相同,但是文件名中的空格会被替换成_
。例如我们在之前的示例中import tutorial
事实上导入的就是tutorial.qll
库模块
库模块
后缀qll
,库模块可以被导入,但是不能包含查询语句。
java
// OneTwoThreeLib.qll
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}
文件本身定义了一个名为OneTwoThreeLib
的库模块,模块的内容里定义了一个OneTwoThree
的类别。在其他文件中可以使用import OneTwoThreeLib
来导入,然后直接使用OneTwoThree
这个类。
查询模块
后缀ql,查询模块不能被导入,必须要有查询语句(select或query谓词)
sql
# OneTwoQuery.ql
import OneTwoThreeLib
from OneTwoThree ott
where ott = 1 or ott = 2
select ott as res
这个文件定义了一个名为OneTwoQuery
的查询模块,模块内容包含了一个导入语句和select子句
显式模块
直接在另一个模块中定义一个新的模块称为显式模块。定义的内容类似于库模块,不能有查询语句。
java
...
module M {
class OneTwo extends OneTwoThree{
OneTwo() {
this = 1 or this = 2
}
}
}
这定义了一个名为M
的显式模块,模块内容定义了名为OneTwo
的类
参数化模块
在后面的进阶学习过程中可能会涉及,可提前查询官方文档
变量
QL中的变量表示一组值,事实上是一个等式公式,等式条件成立即可,不代表任何数据的内存位置,更不是赋值,这与传统编程语言不同,需要区别开来。从理解上来看,变量可以理解为暂存满足这个类型条件的所有的值的集合
,但需要用来受到更进一步的操作和筛选约束,因此变量必须为有限个值。
sql
// correct
from int i
where i in [0..9] // 0 <= i <= 9
select i
// error,无限个值
from int i
select i
变量又分为绑定变量
和自由变量
, 通俗理解,自由变量像是一个待定的代号
,而绑定变量则是已经赋值的符号
python
"hello".indexOf("l") = 1 // 2 and 3, 绑定
min(float f | f in [-3 .. 3]) // -3, 绑定
(i + 7) * 3 // 自由
x.sqrt() // 自由
再来看一组例子
python
"hello".indexOf("l") = 1 // 永远不成立
min(float f | f in [-3 .. 3]) = -3 // 永远成立
(i + 7) * 3 instanceof int // 包含一个自由变量i,成不成立要看i的取值
exists(float y | x.sqrt() = y) // 包含x和y自由变量,但是不管y怎么取值都不影响整个式子是否成立,只与x有关
instanceof表示类型检查,
<expression> instanceof <classname>
,判断是否属于某一类
表达式
这里的理解和其他ool很类似,下面看一些例子即可
java
select count(int i | i = 1 or i = 2 | i)
select concat(int i | i = [0..3] | i.toString() order by i desc) // 把0~3逆序排列并组合
select rank[4](int i | i = [5..15] | i*2) // 取出5~15的第四个数字*2的结果。需要注意,rank起始序号从1开始,而不是0
from int x
where x in [-5 .. 5] and x != 0
select unique(int y | y = x or y = x.abs() | y) // y=x和y=x.abs()相等,才满足unique唯一确定的要求
公式
相等
A != B 和 not A = B意思完全不同,
- A != B 表示A和B不完全相同,即只要存在一对不同的值就成立
- A = B 表示A与B只要有交集即可,即只要存在任何一对值相同就成立
- not A = B 表示完全不相同,即A和B没有任何一对值是相同的就成立,可以看成上一条的相反
1 != [1 .. 2]
成立,因为1 != 2
1 = [1 .. 2]
成立,因为1 = 1
not 1 = [1 .. 2]
不成立,因为有一个共同的值(1
)。可以直接看做上面一条的反义
类型检查
x instanceof Person // 检查x是否为Person类型
范围检查
\<expression> in \<range>
sql
from int i
where i = 2 and i in [1..3.1] // 在 >=1 且 <=3.1的范围内
select i
也可以写成<expression> = <range>
sql
from int i
where i = 2 and i = [1..3.1]
select i
量化
exists
exists(<variable declarations> | <formula 1> and <formula 2>)
sql
exists(int i | i instanceof OneTwoThree and i in [1..2])
也可以写成exists(<variable declarations> | <formula 1> | <formula 2>)
forall
forall(<variable declarations> | <formula 1> | <formula 2>)
表示对于formula1的前提下,是否所有的formula2都满足,如果是,则返回true;
例如:forall(int i | i instanceof OneTwoThree | i < 5)
,含义是在所有OneTwoThree中,是否全都小于5。
逻辑上等于not exists(<variable declarations> | <formula 1> | not <formula 2>)
因此上面那个例子也可以写作:not exists(int i | i instanceof OneTwoThree | not i < 5)
if...then...else
sql
string visibility(Class c){
if c.isPublic()
then result = "public"
else result = "private"
}
注解
以下内容查阅即可
Annotation | 作用 | 用法示例 |
---|---|---|
abstract | 用于定义抽象实体。可以是抽象类或抽象谓词,通常在子类中被重写。 | abstract class Configuration { abstract predicate isSource(Node source); } |
cached | 将实体的计算结果缓存,以提高多次引用的效率。 | cached predicate slowCalculation(int x) { x = complexComputation() } |
deprecated | 标记不推荐使用的名称,通常在文档中提供替代名称。 | deprecated class OldClass { /* DEPRECATED: Use NewClass instead */ } |
external | 用于定义外部模板谓词,通常用于数据库谓词。 | external predicate externalPredicate(int id); |
transient | 用于 external 谓词,表示在评估过程中不缓存到磁盘。 | external transient predicate tempCalculation(int x); |
final | 标记不可重写的名称或不可被子类继承的类。 | final predicate hasName(string name) { name = this.getName() } |
library | 用于标记仅限于 .qll 文件中引用的名称(该注解已弃用)。 | library class InternalClass { } // 使用私有模块代替 |
override | 表示重写基类型中的成员谓词或字段。 | override predicate isSource(Node source) { ... } |
private | 阻止名称被导出,仅在当前模块的命名空间中引用。 | private int secretFunction() { result = 42 } |
query | 将谓词转换为查询,使其成为 QL 程序的输出。 | query predicate findResults() { ... } |
pragma | 用于编译器优化,控制谓词内联等行为。 | pragma[inline] predicate fastCalculation(int x) { x = simpleComputation() } |