0%

CodeQL学习笔记(二)

序言

倚杖柴门外,临门听暮蝉。

学习CodeQL查询语法。

基本查询结构

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
*
* Query metadata
*
*/

import /* ... CodeQL libraries or modules ... */

/* ... Optional, define CodeQL classes and predicates ... */

from /* ... variable declarations ... */
where /* ... logical formula ... */
select /* ... expressions ... */

用户可以在查询过程中自定义谓词和类来自主分析。

一些专有名词:

  • statement 语句

  • predicate 谓词

  • clause 子句

import语句

定于需要导入到查询中的库和模块,以及源码对应的语言模块:

cpp,csharp,go,java,javascript,python

CodeQL为开发者提供了一些库,包含常用的谓词、类型和适用于各种分析的模块,比如控制流、数据流、污点追踪。

三个clause字句

from

1
from <type> <variable name>,...

声明在查询中需要使用的变量

where

对from中声明对变量加以限制的逻辑条件。

select

寻找那些from声明的、满足where条件的所有查询结果。

举个例子,到所有勾股值:

1
2
3
4
5
6
7
8
9
10
import java

from int x, int y, int z, int summary
where
x in [1 .. 10] and
y in [1 .. 10] and
z in [1 .. 10] and
x * x + y * y = z * z and
summary = x + y + z
select x, y, z, summary
image-20211026152559442

告警模板,包含两列:

1
select element, string
  • element:查询结果,查询标识的代码元素,定义危险点的位置。
  • string:为element显附加的报警消息,描述警报的原因。

自定义类:

1
2
3
4
5
6
7
8
9
10
11
12
13
class SmallInt extends int {
SmallInt(){
this in [1..10]
}
int square(){
result = this * this
}
}


from SmallInt x, SmallInt y, SmallInt z
where x.square() + y.square() = z.square()
select x, y, z

QL语言参考

About

QL特点:

  • Declarative 命令式 :描述了结果必须满足的属性,而不是提供计算结果的过程。
  • Object-oriented 面向对象

QL中的class实际上并不是像Java语言一样需要占用一段内存空间,QL中的类是限制已有数据的逻辑属性

谓词 Predicate

关键字predicate,QL中的“函数”,本质上帮助QL程序限制逻辑特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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"
}

规范:

  • 谓词可以在任意地方定义,可以有返回值也可以没有,返回值默认变量为result

  • 谓词名称为小写字母开头,驼峰命名就是一个不错的选择。

  • 参数列表逗号分割,每个参数类型+形参名称。

  • 谓词体,逻辑公式。

抽象谓词和外部谓词是可以没有谓词体的。声明;即可

没有返回值的谓词

1
2
3
4
5
6
7
predicate isSmall(int i){
i in [1..9]
}

from int i
where isSmall(i)
select i

如果i是int,并且在[1~9]之间,那么这个谓词成立。

有返回值的谓词

1
2
3
4
5
6
7
8
int isSmall(int i){
i in [1..9] and
result = i +1
}


from int i
select isSmall(i)

如果i是[1~9]的整数,返回2~10。

对于这个下面这个例子,穷举3个case来返回不同的谓词结果。

1
2
3
4
5
6
7
predicate hasCapital(string country, string capital) {
country = "Belgium" and capital = "Brussels"
or
country = "Germany" and capital = "Berlin"
or
country = "France" and capital = "Paris"
}

递归谓词

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)
}


from string s
where s = "Belgium"
select getANeighbor(s)

这种case下,返回“Belgium”的全部邻居:“France” & “Germany“

谓词的种类

三种

  • 非成员谓词Non-member predicate : class外定义的谓词,不属于任何class
  • 成员谓词:class内定义的常规谓词
  • 特征谓词:简单粗暴,构造函数级别的谓词,直接规定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int getSuccessor(int i) {  // 非成员谓词 non-member predicate
result = i + 1 and
i in [1 .. 9]
}

class FavoriteNumbers extends int {
FavoriteNumbers() { // 特征谓词 Characteristic predicates
this = 1 or
this = 4 or
this = 9
}

string getName() { // 成员谓词 Member predicate
this = 1 and result = "one"
or
this = 4 and result = "four"
or
this = 9 and result = "nine"
}
}

行为绑定

绑定的意思是谓词的参数/结果一定是finite的,一定是有限的。

两个反例:

1
2
3
4
5
6
7
int multiplyBy4(int i) {
result = i * 4
}

predicate shortString(string str) {
str.length() < 10
}

绑定集合 bindingset[]

如何使用非绑定谓词呢?加个bindingset限制就好了。(很像注解)

1
2
3
4
5
6
7
8
bindingset[i]
int multiplyBy4(int i) {
result = i * 4
}

from int i
where i in [1..10]
select multiplyBy4(i)
1
2
3
4
5
6
7
8
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

区别:

  • bindingset[x] bindingset[y] 表示x, y至少有一个是有界的
  • bindingset[x,y] 表示x,y 必须都是有界的

数据库谓词

如果一个数据库内部包含Person表,我们可以用:

1
persons(x, firstName, _, age) //第三个_表示Don‘t care expressioon 表示任意值都可以,不做限制

唯一注意的是不可以自定义数据库谓词,一切要按照数据库提供的谓词去使用

查询 Query

查询是 QL 程序的输出。

select子句

正常模板:

1
2
3
from /* ... variable declarations ... */
where /* ... logical formula ... */
select /* ... expressions ... */

from/select是可选的

select子句中除了表达式之外,还可以加:

  • as as后面加label,相当于给查询结果某一列加了label,后续可以继续使用该label
  • order by 排序 ; order by x asc 升序;order by y desc 降序;

举例子:

1
2
3
from int x, int y
where x = 3 and y in [0 .. 2]
select x, y, x * y as product, "product: " + product

按照y的降序排列:

1
2
3
from int x, int y
where x = 3 and y in [0 .. 2]
select x, y, x * y as product order by y desc

查询谓词

查询谓词是带有query注释的非成员谓词。

举例:

1
2
3
4
5
query int getProduct(int x, int y){
x = 3 and
y in [0..2] and
result = x * y
}

直接执行就行,不用写select,或者硬要写的话,用select getProduct(_,_)

优点:

我们可以在其他的谓词内部使用这种查询谓词:

1
2
3
class MultipleOfThree extends int {
MultipleOfThree(){ this = getProduct(_,_)}
}

类型 Type

QL是一门静态类型语言,所以每一个变量都是拥有自己的声明类型的。

一个type是一组value;比如说,int是整数集合,代表所有的整数;

需要注意的是,一种value可以属于多个集合,也就是属于多种type。

Type有五种:数据库类型、class类型、字符类型、类域(class domain)类型

原始类型 primitive types

原始类型属于QL内置,应用于全局namespace的一种类型。

  • boolean:true or false
  • float:64位浮点数
  • int:32位整数
  • string:字符串
  • date:日期

QL为这些原始类型提供了很多build-in方法

可以通过在适当类型的表达式上使用 dispatch 来获得恰当的方法。

类Classes

用户可以制定自己的type,可直接定义一个类。

类存在的目的:

  1. 将一些相关的value聚合到一起,使他们成为成员
  2. 为这些成员创建成员谓词
  3. 定义子类覆盖成员谓词,继承所有父类谓词

QL 中的类不会“创建”一个新对象,它只是表示一个逻辑属性。如果某值满足特定的逻辑属性,则该值在这个特定类中。

1
2
3
4
5
6
7
8
9
10
11
12
13
class OneTwoThree extends int {
OneTwoThree() { // characteristic predicate
this = 1 or this = 2 or this = 3
}

string getAString() { // member predicate
result = "One, two or three: " + this.toString()
}

predicate isEven() { // member predicate
this = 2
}
}

注意:

  1. QL中自定义的类一定要至少extends一个父类,被extends的父类被称为base types
  2. QL中的类允许多继承
  3. 不得extends final类

类内成分

  • 特征谓词 (也就是“构造函数”
  • field声明
  • 成员谓词
  • 继承自父类的所有非私有成员谓词和字段,可以在本类中override
特征谓词Characteristic predicate

使用this关键字来对类中可能的逻辑属性进行限制

成员谓词Member predicate

可以通过“对象”调用,也可以使用(类名).func()强制进行cast调用

字段Field

成员属性

1
2
3
4
5
6
7
8
9
10
11
12
13
class SmallInt extends int {
SmallInt() { this = [1 .. 10] }
}

class DivisibleInt extends SmallInt {
SmallInt divisor; // declaration of the field `divisor`
DivisibleInt() { this % divisor = 0 }

SmallInt getADivisor() { result = divisor }
}

from DivisibleInt i
select i, i.getADivisor()

SmallInt类表示的是[1..10]之间的整数,DivisibleInt继承了SmallInt,结果就是每个数字的因数分解

具体类Concrete classes

上面提到全部是具体类,拥有实际成分。

抽象类Abstract classes

使用abstract修饰的类

例子:

假设你对所有可以解释为 SQL 查询的表达式感兴趣,可以自己加一层抽象类。

1
2
3
abstract class SqlExpr extends Expr {
...
}

接下来就可以为每一种数据库类型定义各种之类class PostgresSqlExpr extends SqlExpr

Override 成员谓词

在谓词前面,添加override注释就行

1
2
3
4
5
6
7
8
9
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}

override string getAString() {
result = "One or two: " + this.toString()
}
}

1,2会按照OneTwo来走,3会按照老方法来走,因为逻辑限制不一样。

多继承

一个类可以继承多种type。在这种情况下,它继承自所有这些类型。

比如说:

1
class Two extends OneTwo, TwoThree {}

Two必须满足逻辑属性OneTwo, 和TwoThree。这里的类Two包含一个值,2。

它还(间接)继承自OneTwoThreeint

非继承子类型Non-extending subtypes

一个类可以声明与其他类型的instanceof关系

class A instanceof B

并不是代表B是A的父类

官方说法是:

class A extends B:B称为base type

class C instance of D:D称为supertypes

子类并不会继承supertype的任何成员谓词

下面这个例子中,Bar类可以使用super的关键字调用Foo类中的foo_method()方法

1
2
3
4
5
6
7
8
9
class Foo extends int {
Foo() { this in [1 .. 10] }

string foo_method() { result = "foo" }
}

class Bar instanceof Foo {
string toString() { result = super.foo_method() }
}

在这个例子中,Foo的特征谓词也适用于Bar。然而,foo_method在Bar中没有暴露,所以查询select any(Bar b).foo_method()的结果是编译时出错。从这个例子中可以看出,仍然可以用super关键字访问instanceof supertypes的方法。

intanceof的类其实并不属于子类的一部分,不参与子类,不可以override。

下面这个例子中,方法Bar::foo不是重写Foo::foo。相反,它只是重写Interface::foo。这意味着select any(Foo f).foo()只返回foo。

如果Bar被定义为extends Foo,那么select any(Foo b)将产生bar。

1
2
3
4
5
6
7
8
9
10
11
12
13
 class Interface extends int {
Interface() { this in [1 .. 10] }
string foo() { result = "" }
}

class Foo extends int {
Foo() { this in [1 .. 5] }
string foo() { result = "foo" }
}

class Bar extends Interface instanceof Foo {
override string foo() { result = "bar" }
}

模块 Module

模块提供了一种通过将相关类型、谓词和其他模块组合在一起来组织 QL 代码的方法。

可以将模块导入到其他文件中,这样可以避免重复,并有助于将您的代码组织成更易于管理的部分。

定义一个模块

here is an example of the simplest way,显示模块

1
2
3
4
5
6
7
module Example {
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}
}

只可以对显示模块加注解,不能注解文件模块

模块种类

文件模块

每个查询文件.ql和库文件.qll都属于是文件模块,模块名称就是文件名(文件名中的空格被替换为_)。

文件的内容构成了模块的主题。

库模块

每个.qll文件都是一个库模块,库模块是一种文件模块。

OneTwoThreeLib.qll

1
2
3
4
5
class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}
查询模块

每个.ql文件都是一个查询模块。查询模块是一种文件模块。

  • 查询模块不可以被import。

  • 查询模块一定要至少有一次查询,select或者query predicate都行

OneTwoQuery.ql

1
2
3
4
5
import OneTwoThreeLib

from OneTwoThree ott
where ott = 1 or ott = 2
select ott

显示模块

可以在模块中定义一个模块。

我们可以在上面的OneTwoThreeLib.qll文件中定义一个模块:

1
2
3
4
5
6
7
module M {
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
}
}

模块体

模块中可以包含以下结构:

  1. import 语句
  2. predicates
  3. types 包括用户定义的class
  4. alias 别名
  5. 显示模块
  6. select语句 仅在查询模块中可用

导入模块

当导入一个模块时,会将其命名空间中的所有名称(私有名称除外)带入当前模块的命名空间中。

1
2
import <module_expression1> as <name>
import <module_expression2>

可以起别名 用as关键字

1
import javascript as js

别名 Alias

QL 实体的替代名称。

变量 Variable

QL 中变量的使用方式与代数或逻辑中的变量类似。变量代表一组值,这些值通常受公式限制。

声明一个变量

type+name

Free variable & bound variable

1
2
3
4
5
6
7
"hello".indexOf("l") // 2 and 3

min(float f | f in [-3 .. 3]) // bound

(i + 7) * 3 // free

x.sqrt() // free

表达式 Expression

表达式计算一组值并具有类型。

表达式的种类:

  • module expression
  • type expression
  • predicate expression

关键字

  • Boolean : true false
  • Integer:整数(正,负)
  • Float:浮点数(正,负)
  • String:字符串

The following string formats are recognized as dates:

  • ISO dates, such as "2016-04-03 17:00:24". The seconds part is optional (assumed to be "00" if it’s missing), and the entire time part can also be missing (in which case it’s assumed to be "00:00:00").
  • Short-hand ISO dates, such as "20160403".
  • UK-style dates, such as "03/04/2016".
  • Verbose dates, such as "03 April 2016".

范围Range

[3..7] 表示 3,4,5,6,7

自定义set:

前十个素数:[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Super

和Java中的super一样,用来调用super types中的同名方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class A extends int {
A() { this = 1 }
int getANumber() { result = 2 }
}

class B extends int {
B() { this = 1 }
int getANumber() { result = 3 }
}

class C extends A, B {
// Need to define `int getANumber()`; otherwise it would be ambiguous
int getANumber() {
result = B.super.getANumber()
}
}

from C c
select c, c.getANumber()

调用谓词 with result

调用有返回值的谓词本身就是表达式 a.getAChild()

调用没有返回值的谓词是公式formula

聚合

聚合是一种映射,它根据公式指定的一组输入值计算结果值。

1
<aggregate>(<variable declarations> | <formula> | <expression>)

fromula加以限制,expression计算结果值,aggregate算出来想要的目标

关键词:

排序关键词:minmaxrankconcatstrictconcat

排序是可以自动选择按照数值排序,还是按照字符排序(unicode value)。

特殊需求排序:order by <element> [desc/asc] 缺省是asc升序排列

比如:order by o.getName()

charAt() 返回值是去重的

一些例子:

  1. count:确定<expression>不同结果的个数

    1
    count(File f | f.getTotalNumberOfLines() > 500 | f) // 寻找文件行数大于500行的文件数
  2. minmax<expression>的最小、最大值

    <expression> 必须是数字类型或者string类型

    聚合返回所有js文件中文件行数最多的文件名

    1
    max(File f | f.getExtension() = "js" | f.getBaseName() order by f.getTotalNumberOfLines(), f.getNumberOfLinesOfCode())
  3. avg<expression> 的平均值

    <expression>结果必须的是数值。

    1
    avg(int i | i =[0..3]|i) // 0,1,2,3的平均值
  4. sum<expression> 的总和

    <expression>结果必须的是数值。

    1
    sum(int i, int j| i in [0..2] and j in [3..5]| i*j)
  5. concat:将<expression>的各种结果都聚合到一起。

    <expression>结果必须是string。

    1
    2
    select concat(int i| i in [0..3] | i.toString() order by i desc) // 3210
    select concat(int i| i in [0..3] | i.toString(),"|" order by i desc) // 3|2|1|0
  6. rank

    <expression>的结果排序。

    1
    2
    rank[4](int i | i = [1..10] | i) // 4
    select rank[4](int i | i = [1 .. 10] | i order by i desc) // 7

    [index]从1开始,rank[0] has no result

    rank[1] == min(…)

  7. strictconcatstrictcountstrictsum

    没有满足formula的,就返回empty set,而不是默认的0或者空字符串

  8. unique

    返回<expression>的结果集中满足formula的唯一值。

    1
    2
    3
    from int x
    where x in [-5 .. 5] and x != 0
    select unique(int y | y = x or y = x.abs() | y)

    [1..5]满足的x和x.abs()的结果是唯一的

单调聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
string getPerson() { result = "Alice" or
result = "Bob" or
result = "Charles" or
result = "Diane"
}
string getFruit(string p) { p = "Alice" and result = "Orange" or
p = "Alice" and result = "Apple" or
p = "Bob" and result = "Apple" or
p = "Charles" and result = "Apple" or
p = "Charles" and result = "Banana"
}
int getPrice(string f) { f = "Apple" and result = 100 or
f = "Orange" and result = 100 or
f = "Orange" and result = 1
}

predicate nonmono(string p, int cost) {
p = getPerson() and cost = sum(string f | f = getFruit(p) | getPrice(f))
}

language[monotonicAggregates]
predicate mono(string p, int cost) {
p = getPerson() and cost = sum(string f | f = getFruit(p) | getPrice(f))
}

from string variant, string person, int cost
where variant = "default" and nonmono(person, cost) or
variant = "monotonic" and mono(person, cost)
select variant, person, cost
order by variant, person
image-20211028120610724

一种苹果,两种橙子

区别:

  1. 非单调谓词:Alice买了苹果和橙子,sum会把这些一起求和100+100+1=201
  2. 单调谓词:Alice会分别计算100+1=101,100+100=200

Charies想买苹果+香蕉,但是香蕉根本不卖

非单调谓词:直接返回苹果+0

单词谓词:因为香蕉的值没有,所以直接不算

Diane什么都不买,所以都显示0。但如果用strictsum,两种谓词都不显示

单调谓词普遍用于递归,因为每一轮结果都是有意义(fix-point)。

递归单调聚合

单调聚合函数经常用来递归,这种递归通常应用在表达式中。

举例:计算当前节点到叶子结点的距离:

1
2
3
4
5
int depth(Node n) {
if not exists(n.getAChild())
then result = 0
else result = 1 + max(Node child | child = n.getAChild() | depth(child))
}

Any

1
any(<variable declarations> | <formula> | <expression>)
image-20211028173634119

Cast 类型强转

两种方式:

  • postfix 后缀,x.(Foo) 表示将x限制为Foo类型
  • prefix 前缀,(Foo)x 也表示将x限制为Foo类型

一般用法:x.(Foo) 等同于((Foo)x)

例子:查找父类是List的类

1
2
3
4
5
import java

from Type t
where t.(Class).getASupertype().hasName("List")
select t

无关表达式 Don’t-care expression

参数单个下划线表示,表示any value,不关心实际是什么

例子:

1
2
3
4
5
from string s
where s = "hello".charAt(_)
select s

//l,o,e,h

公式 Formula

Formulas define logical relations between the free variables used in expressions.

Formula限制了表达式中那些free类型的变量。

当一个formula为true,习惯说是hold住了

hold:

“Ann” < “Anne”

[1 .. 2] = [2 .. 5]

Type check

1
<expression> instanceof <type>

range check

1
2
<expression> in <type>
<expression> = <type>

两种形式表达都可以

量化公式

显式量词

exists

1
exists(<variable declarations> | <formula1> and <formula2>)

forall

1
forall(<variable declarations> | <formula 1> | <formula 2>)

hold formula1的全部变量也能够hold formula2 那么整个formula就hold

forex

1
forex(<variable declarations> | <formula 1> | <formula 2>)

等同于

1
2
forall(<vars> | <formula 1> | <formula 2>) and
exists(<vars> | <formula 1> | <formula 2>)

隐式量词

Don’t-care expression

逻辑连接词

比如:

  • not
  • if…then…else
  • and
  • or
  • implies

重点说一下implies

A implies B 表示的是 (not A) or B

举例子:找到[1..10]之内 是奇数 或者 是4的倍数

1
2
3
4
5
6
class SmallInt extends int{
SmallInt(){ this in [1..10]}
}
from SmallInt x
where x % 2 = 0 implies x % 4 = 0
select x

注解 Annotation

注释是一个字符串,可以直接放在 QL 实体或名称的声明之前。

私有类:

1
2
3
private module M {
...
}

一些注解可以作用于实体,另一些作用于实体的特定名称

作用于实体:

  • abstract, cached, external, transient, final, override, pragma, language, and bindingset

作用于名称:

  • deprecated, library, private, and query

abstract

Available for: classes , member predicates 可用于:类, 成员谓词

抽象谓词 abstract predicate

have no body,一定要被子类去overirde

1
2
3
4
5
6
abstract class Configuration extends string {
...
/** Holds if `source` is a relevant data flow source. */
abstract predicate isSource(Node source);
...
}

自定义数据流分析,之后可以对不同的case,实现不同的子类override多种的isSource

1
2
3
4
5
6
7
8
9
class ConfigA extends Configuration {
...
// provides a concrete definition of `isSource`
override predicate isSource(Node source) { ... }
}
class ConfigB extends ConfigA {
...
// doesn't need to override `isSource`, because it inherits it from ConfigA
}

cached

Avilable for:class , algebraic datatypes , 特征谓词,成员谓词,非成员谓词, 模块

不怎么用 没细看。保存select到缓存中。

deprecated

Avilable for:class , algebraic datatypes , 成员谓词,非成员谓词, 类成员,模块,别名

表示过时,不建议使用的版本。

external

Avilable for:非成员谓词

外部模板谓词,常用于数据库谓词

trainsient

Avilable for:非成员谓词

查询不会持久化存储在磁盘上

final

Avilable for:类,成员谓词,类成员

表示被修饰的不可以被重写或者继承,final类不可以被任何类继承。

1
2
3
4
class Element ... {
string getName() { result = ... }
final predicate hasName(string name) { name = this.getName() }
}

override

Avilable for:成员谓词,类成员

private

Avilable for:类,代数类型,成员谓词,非成员谓词,import,类成员,模块,别名

query

Avilable for:非成员谓词,别名

将一个谓词转换为查询

编译器参数

优化执行性能。

Inling 内联

对于简单的谓词,QL优化器有时会用谓词体本身取代对谓词的调用,这就是所谓的内联。

例如,假设你有一个定义谓词one(int i) { i = 1 }和一个对该谓词的调用…one(y) …. QL优化器可以将该谓词内联为…y = 1 ….

你可以使用以下编译器pragma注释来控制QL优化器内联谓词的方式。

pragma[inline]

告诉QL优化器将这个谓词内联到它被调用的地方。当一个谓词体完全计算起来非常昂贵时,这可能很有用,因为它可以确保谓词在被调用的地方与其他上下文信息一起被评估。

pragma[noinline]

用于防止一个谓词被内联到它被调用的地方。在实践中,当你已经在一个 “辅助 “谓词中把某些变量组合在一起时,这个注解是有用的,以确保关系被整体评估。这可以帮助提高性能。QL优化器的内联可能会撤销辅助谓词的工作,所以用pragma[noinline]来注释它是个好主意。

pragma[nomagic]

用于防止QL优化器在谓词上执行 “魔法集 “优化。

这种优化包括从谓词调用的上下文中获取信息并将其推入谓词的主体。这通常是有益的,所以你不应该使用pragma[nomagic]注解,除非GitHub推荐这样做

请注意,nomagic也就意味着noinline。

pragma[noopt]

用于阻止QL优化器对谓词进行优化,除非它对编译和评估的工作绝对必要。

这很少有必要,你不应该使用pragma[noopt]注解,除非GitHub推荐这样做,例如,为了帮助解决性能问题。

pragma[only_bind_out]

可以让你指定QL编译器应该绑定表达式的方向。这对于在QL优化器以低效方式排列QL程序的部分的罕见情况下提高性能是很有用的。

例如,x = pragma[only_bind_out](y)在语义上等同于x = y,但有不同的绑定行为。x = y从y绑定x,反之亦然,而x = pragma[only_bind_out](y)只从y绑定x。

欲了解更多信息,请参见 “Binding“。

pragma[only_bind_into]

指定QL编译器应该绑定表达式的方向。这对于在QL优化器以低效率的方式排列QL程序的部分的罕见情况下提高性能是很有用的。

例如,x = pragma[only_bind_into](y)在语义上等同于x = y,但有不同的绑定行为。x = y从y绑定x,反之亦然,而x = pragma[only_bind_into](y)只从x绑定y。

欲了解更多信息,请参见 “Binding“。

bindingset[…]

https://codeql.github.com/docs/ql-language-reference/predicates/#binding-behavior

明确说明谓词或类的绑定集。绑定集是谓词或类主体的参数的一个子集,如果这些参数被限制在一个有限的值集上,那么谓词或类本身就是有限的(也就是说,它被评估为一个有限的图元集)。

bindingset 注解接受一个用逗号分隔的变量列表。

当您注释一个谓词时,每个变量必须是谓词的参数,可能包括this(对于特征谓词和成员谓词)和result(对于返回结果的谓词)。
当你注解一个类时,每个变量必须是this或该类中的一个字段。从CodeQL CLI的2.3.0版和LGTM Enterprise的1.26版开始支持类的绑定集。

递归

传递闭包 Transitive closures

原始谓词必须要有两个参数,this 和 result, 参数类型必须兼容

传递闭包 +

一次或者多次

1
2
3
4
5
6
7
Person getAnAncestor() {
result = this.getAParent()
or
result = this.getAParent().getAnAncestor()
}

等价于getparent+() 返回p的所有祖先(不包含p)

自反传递闭包 *

0次或者多次

1
2
3
4
5
6
7
Person getAnAncestor2() {
result = this
or
result = this.getAParent().getAnAncestor2()
}

等价于getAParent*() 返回p的所有祖先(包含p)

名称解析 Name resolution

QL将名称解析为程序元素。

名称

1
import javascript

标准化引用

模块表达使用.来 导入相对路径下的其他module

举例Example.ql

1
import examples.security.MyLibrary

QL如何寻找:

  1. 首先将所有的.替换为当前操作系统的文件分割符,所以QL首先在Example.ql文件目录下寻找examples/security/MyLibrary.qll文件,找到了就import。
  2. 如果没找到,QL会去包含qlpack.yml文件的目录下寻找examples/security/MyLibrary.qll文件。
  3. 如果还没找到,那么就去每个库目录下进行寻找,库目录就是qlpack.yml定义的libraryPathDependencies属性的值。

选择

可以使用选择来引用特定模块内的模块、类、或谓词。

1
<module_expression>::<name>

举例:

libs/CountriesLib.qll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Countries extends string {
Countries() {
this = "Belgium"
or
this = "France"
or
this = "India"
}
}

module M{
class EuropeanCountries extends Countries {
EuropeanCountries(){
this = "Belgium"
or
this = "France"
}
}
}
image-20211028224602517

CustomModule.ql:

1
2
3
4
import libs.CountriesLib // qll文件名

from M::EuropeanCountries ec // qll文件中的模块自然就被引用了,可以直接选择M
select ec

第二种:只导入M模块中的内容

1
2
3
4
import libs.CountriesLib::M

from EuropeanCountries ec
select ec

命名空间

实体:module type predicate

global namespace

包含所有内置实体的区域都叫做global namespace

QL自动引入,直接使用关键字就行,不用import任何东西

local namespace

对于之前提到的M来说,主要分为三种:

  1. declared:M模块中声明的任何内容
  2. imported:谁import了M模块,那么自动也会import M模块中的import
  3. exported

举例:OneTwoThreeLib.qll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import MyFavoriteNumbers

class OneTwoThree extends int {
OneTwoThree() {
this = 1 or this = 2 or this = 3
}
}

private module P {
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
}
}

MyFavoriteNumbers内部import的任何东西都被当前OneTwoThreeLib.qll import

OneTwoThreeLib declare了两个成分:class OneTwoThree 和 private module P

OneTwoThreeLib export了class OneTwoThree 以及任何在MyFavoriteNumbers export内容

由于module P 是private修饰的 所以不可以被export

QL程序的演变 evolution

过程

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

QL的查询结果都是元组tuple

QL语言规范

太多了,挑一些重点

Types

QL中的type包括:

基础类型(5种)、数据库类型、类类型、字符类型、类域(class domain)类型

Types in QL are either primitive types, database types, class types, character types or class domain types.

Within the specification the class type for C is written C.class, the character type is written C.C and the domain type is written C.extends. However the class type is still named C.

Value

5种基础类型 int float string boolean date + 1种 entity

Key Words

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
and
any
as
asc
avg
boolean
by
class
concat
count
date
desc
else
exists
extends
false
float
forall
forex
from
if
implies
import
in
instanceof
int
max
min
module
newtype
none
not
or
order
predicate
rank
result
select
strictconcat
strictcount
strictsum
string
sum
super
then
this
true
unique
where

Annotation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
annotations ::= annotation*

annotation ::= simpleAnnotation | argsAnnotation

simpleAnnotation ::= "abstract"
| "cached"
| "external"
| "final"
| "transient"
| "library"
| "private"
| "deprecated"
| "override"
| "query"

argsAnnotation ::= "pragma" "[" ("inline" | "noinline" | "nomagic" | "noopt") "]"
| "language" "[" "monotonicAggregates" "]"
| "bindingset" "[" (variable ( "," variable)*)? "]"
image-20211029120929628 image-20211029120939113

Built-in内置

image-20211029121432146 image-20211029121416854 image-20211029121447367

built-ins-for-float

built-ins-for-int

built-ins-for string