QL语言参考-3类型
类型(Types)
QL 是一种静态类型的语言,因此每个变量都必须有一个声明的类型。
类型是一组值。例如,int 类型是一组整数。注意,一个值可以属于这些集合中的多个,这意味着它可以有多个类型。
QL 中的类型有基本类型、类、字符类型、类域类型、代数数据类型、类型联合和数据库类型。primitive types, classes, character types, class domain types, algebraic datatypes, type unions, and database types.
基本类型(Primitive types)
这些类型是构建在 QL 中的,并且总是在全局名称空间中可用,与要查询的数据库(就是需要审查的项目生成的AST数据库),也就是说基本类型与项目无关。
- 布尔类型boolean:此类型包含true和false值。
- 浮点类型float:这种类型包含64位浮点数,例如6.28和-0.618。
- 整数类型int:这种类型包含32位2的补码整数,例如-1和42。
- 字符串string:这种类型包含16位字符的有限字符串。
- 日期date:此类型包含日期(以及可选的时间)。
QL 有一系列基本类型上定义的内置操作。可以通过对适当类型的表达式使用 dispatch
来实现这些功能。
例如,1.toString ()
是整数常量1的字符串表示形式。有关 QL 中可用的内置操作的完整列表,请参阅 QL 语言规范中关于内置操作 的部分。
类(Classes)
可以在 QL 中定义自己的类型。
类提供了一种简单的方法来重用和构造代码。例如,你可以:
- 将相关的值分组
- 在这些值上定义成员谓词
- 重写成员谓词的子类
注意:QL 中的类并不“创建”一个新对象,它只是表示一个逻辑属性。如果值满足该逻辑属性,则该值属于特定类。
定义类
要定义一个类,可以这样写:
-
使用class关键字
-
指定类的名字,类名的首字母用大写
-
指定继承的type
-
类的body部分
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
}
}
这定义了一个类 OneTwoThree,其中包含值1、2和3。特征谓词捕获“作为整数1、2或3之一”的逻辑属性
OneTwoThree 扩展 int,也就是说,它是 int 的一个子类型。
注意:QL 中的类必须总是至少有一个超类型。使用 extends 关键字引用的超类型称为类的基类型。类的值包含在超类型的交集中(也就是说,它们在类域类型中)。类从其基类型继承所有成员谓词。
一个类可以扩展多种类型。有关更多信息,请参阅《多重继承类》,类也可以专门化其他类型,而不需要通过 instanceof 扩展类接口,参见“非扩展子类型”.
有效的一个类:
- Must not extend itself. 不能继承扩展自己
- Must not extend a final class. 不能继承与final类
- Must not extend types that are incompatible. For more information, see “Type compatibility.” 不能继承不兼容的类型。
您还可以对一个类进行注释。请参阅可用于类的注释列表。
Class bodies
类的主体可以包含:
- 特征谓词(构造函数)的声明。
- 任意数量的成员谓词(成员函数)声明。
- 任意数量的字段声明(成员变量)。
定义类时,该类还从其超类型继承所有非私有成员谓词和字段。您可以重写这些谓词和字段,以给出更具体的定义。
特征谓词(Characteristic predicates)
这些谓词是在类体内定义的。它们是逻辑属性,使用变量 this 限制类中的可能值。
成员谓词(Member predicates)
这些谓词只适用于特定类的成员。可以对值调用成员谓词。例如,你可以使用上面类中的成员谓词:
1.(OneTwoThree).getAString()
这个调用返回结果“ One,two or three: 1”
。
表达式(OneTwoThree)是一个强制转换。它确保1的类型是 OneTwoThree 而不是 int。因此,它可以访问成员谓词 getAString ()。
成员谓词特别有用,因为您可以将它们链接在一起。例如,你可以使用 toUpperCase () ,一个为字符串定义的内置函数:
1.(OneTwoThree).getAString().toUpperCase()
这个调用返回 "ONE, TWO OR THREE: 1"
.
注意
特征谓词和成员谓词通常使用 this
变量。这个变量总是引用类的一个成员,在这种情况下,这个值属于类 OneTwoThree
。在特征谓词中,这个变量约束类中的值。在成员谓词中,这与谓词的任何其他参数的行为方式相同。
字段(Fields)
类似成员变量。这些是在类体中声明的变量。一个类可以在其主体中包含任意数量的字段声明(即变量声明)。您可以在类内的谓词声明中使用这些变量。与变量 this 非常相似,字段必须在特征谓词中进行约束。
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 因子引入了一个字段因子,在特征谓词中约束它,然后在成员谓词 getadavors 的声明中使用它。这类似于通过在 from 部分中声明变量来在 select 子句中引入变量。
具体类(Concrete classes)
上面例子中的类都是具体的类。具体类表示的数值集合是:满足该类特征谓词的数值集合与base type表示的数值集合的交集。
抽象类(Abstract classes)
可以使用abstrace关键字来标注一个类是抽象类。
抽象类表示的数值集合是:满足该类特征谓词的数值集合与所有子类表示的数值集合的并集。
如下是内置库中QueryInjection.ql中定义的QueryInjectionSink抽象类
如果您正在编写一个安全查询,您可能对识别所有可以解释为 SQL 查询的表达式感兴趣。你可以使用下面的抽象类来描述这些表达式:
abstract class SqlExpr extends Expr {
...
}
注意:在向现有抽象类添加新子类时,必须小心。添加子类并不是一个孤立的变更,它还扩展了抽象类,因为这是它的子类的一个联合。
重写成员谓词
如果类从超类型继承成员谓词,则可以重写继承的定义。使用override
关键词
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
override string getAString() {
result = "One or two: " + this.toString()
}
}
成员谓词 getAString ()覆盖了来自 OneTwoThree 的 getAString ()的原始定义。
现在,考虑下面的查询:
from OneTwoThree o
select o, o.getAString()
# | o | [1] |
---|---|---|
1 | 1 | One or two: 1 |
2 | 2 | One or two: 2 |
3 | 3 | One, two or three: 3 |
注意:在 QL 中,与其他面向对象语言不同,相同类型的不同子类型不需要分离。
例如,你可以定义 OneTwoThree 的另一个子类,它与 OneTwo 重叠:
class TwoThree extends OneTwoThree {
TwoThree() {
this = 2 or this = 3
}
override string getAString() {
result = "Two or three: " + this.toString()
}
}
# | o | [1] |
---|---|---|
1 | 1 | One or two: 1 |
2 | 2 | One or two: 2 |
3 | 2 | Two or three: 2 |
4 | 3 | Two or three: 3 |
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
}
}
class OneTwo extends OneTwoThree {
OneTwo() {
this = 1 or this = 2
}
override string getAString() {
result = "One or two: " + this.toString()
}
}
class TwoThree extends OneTwoThree {
TwoThree() {
this = 2 or this = 3
}
override string getAString() {
result = "Two or three: " + this.toString()
}
}
from OneTwoThree o
select o, o.getAString()
多重继承
一个类可以扩展多个类型。在这种情况下,它继承自所有这些类型。
例如,使用上面一节中的定义:
class Two extends OneTwo, TwoThree {}
第二类中的任何值都必须满足 OneTwo
表示的逻辑属性和 TwoThree
表示的逻辑属性。在这里,类Tow
包含一个值,即2。
它继承了 OneTwo 和 TwoThree 的成员谓词,也(间接地)继承了 OneTwoThree
和 int
。
注意:如果子类继承同一谓词名称的多个定义,则必须重写这些定义以避免歧义。超级表达式在这种情况下常常很有用。
不扩展的子类型
使用关键词instantof
Non-extending subtypes
除了扩展基类型之外,类还可以声明与其他类型的实时关系。将一个类声明为 Foo
的 instanceof
,大致等同于在特征谓词中声明 Foo
的 instanceof
。主要的区别是,你可以通过超级调用 Bar
上的方法,你可以得到更好的优化。
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 中公开,因此查询任何any(Bar b).foo_method()
会导致编译时错误。从示例中注意,仍然可以从带有 super 关键字的专门化类中的 instanceof 超类型访问方法。
关键的是,超类型的 instanceof 不是基类型。这意味着这些超类型不参与重写,而且这些超类型的任何字段都不是新类的一部分。当涉及到复杂的类层次结构时,这对方法解析有影响。下面的示例演示了这一点。
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" }
}
在这里,方法 Bar::Foo
不会覆盖 Foo::Foo
。相反,它只覆盖 Interface::foo
。这意味着选择任意(Foo f).Foo ()
只产生 foo
。如果 Bar
被定义为 extends Foo
,那么选择 any (Foo b)
就会产生 bar
。