QL语言参考-9注解
注解(Annotations)
注释是一个字符串,可以直接放在 QL 实体或名称的声明之前。
例如,要将模块 m 声明为私有,可以使用:
private module M {
...
}
注意,有些注释作用于实体本身,而其他注释作用于特定的实体 实体:
- 按照一个 实体:
abstract
,cached
,external
,transient
,final
,override
,pragma
, ,及bindingset
- 按照一个 名称:
deprecated
,library
, ,及query
例如,如果用 private 注释一个实体,那么只有该特定名称是 private。您仍然可以使用不同的名称(使用别名)访问该实体。另一方面,如果使用缓存注释一个实体,则该实体本身将被缓存。
这里有一个明确的例子:
module M {
private int foo() { result = 1 }
predicate bar = foo/0;
}
在这种情况下,查询选择 m: : foo ()
会给出一个编译器错误,因为名称 foo 是 private。查询选择 m:: bar()
是有效的(给出结果1) ,因为名称栏是可见的,它是谓词 foo 的别名。
可以将缓存应用于 foo,但不应用于 bar,因为 foo 是实体的声明。
注释概述
abstract
可用于: 类、成员谓词
抽象注释用于定义抽象实体。
有关抽象类的信息,请参见“类”
抽象谓词是没有主体的成员谓词。它们可以在任何类上定义,并且应该在非抽象子类型中重写。
下面是一个使用抽象谓词的示例。在 QL 中编写数据流分析时,一个常见的模式是定义配置类。这样的配置必须描述它所跟踪的数据源。所有这些配置的超类型可能看起来像这样:
abstract class Configuration extends string {
...
/** Holds if `source` is a relevant data flow source. */
abstract predicate isSource(Node source);
...
}
然后,您可以定义 Configuration 的子类型,它继承了谓词 isSource,以描述特定的配置。任何非抽象子类型都必须(直接或间接)覆盖它来描述它们每个跟踪的数据源。
换句话说,所有扩展 Configuration 的非抽象类都必须在自己的主体中覆盖 isSource,否则它们必须从另一个覆盖 isSource 的类继承:
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
可用于: 类、代数数据类型、特征谓词、成员谓词、非成员谓词、模块
缓存的注释表明,应当对实体进行整体计算并将其存储在计算缓存中。以后对这个实体的所有引用都将使用已经计算过的数据。这会影响来自其他查询以及当前查询的引用。
例如,缓存一个需要很长时间才能进行评估的谓词,并在许多地方重用,这样会很有帮助。
你应该小心使用缓存,因为它可能有意外后果。例如,缓存谓词可能会占用大量存储空间,并且可能会阻止 QL 编译器根据使用它的每个位置的上下文优化谓词。但是,这可能是一个合理的折衷,因为只需要计算一次谓词。
如果使用缓存对类或模块进行注释,则其主体中的所有非私有实体也必须使用缓存进行注释,否则将报告编译器错误。
deprecated
可用于: 类、代数数据类型、成员谓词、非成员谓词、字段、模块、别名
不推荐的注释应用于过时的名称,并计划在将来的 QL 版本中删除这些名称。如果您的任何 QL 文件使用不推荐的名称,您应该考虑重写它们以使用更新的替代名称。通常,弃用的名称会有一个 QLDoc 注释,告诉用户他们应该使用哪个更新的元素。
例如,DataFlowNode 这个名称已经过时,它有以下 QLDoc 注释:
/**
* DEPRECATED: Use `DataFlow::Node` instead.
*
* An expression or function/class declaration,
* viewed as a node in a data flow graph.
*/
deprecated class DataFlowNode extends @dataflownode {
...
}
在 QL 编辑器中使用名称 DataFlowNode 时,会出现这个 QLDoc 注释。
external
可用于: 非成员谓词
外部注释用于谓词,用于定义外部“模板”谓词,这类似于数据库谓词。
transient
可用于: 非成员谓词
瞬态注释应用于也用外部注释的非成员谓词,以表明在计算期间不应将它们缓存到磁盘。注意,如果您尝试在没有外部的情况下应用 transient,编译器将报告错误。
final
可用于: 类、成员谓词、字段
最后的注释应用于无法重写或扩展的实体。换句话说,最后一个类不能作为任何其他类型的基类型,并且最后一个谓词或字段不能在子类中被重写。
如果您不希望子类改变特定实体的含义,那么这是非常有用的。
例如,如果元素具有名称,谓词 hasName (字符串名称)将保留。它使用谓词 getName ()来检查这一点,子类更改这个定义是没有意义的。在这种情况下,hasName 应该是最终的:
class Element ... {
string getName() { result = ... }
final predicate hasName(string name) { name = this.getName() }
}
library
适用于: 班级
重要事项
不推荐使用此注释。不要使用库注释名称,而是将其放在私有(或私有导入)模块中。
库注释应用于只能从。Qll 文件。如果您尝试从没有。Qll 扩展,则 QL 编译器返回一个错误。
override
可用于: 成员谓词,字段
覆盖注释用于指示定义覆盖基类型的成员谓词或字段。
如果重写谓词或字段而没有对其进行注释,则 QL 编译器会发出警告。
private
可用于: 类、代数数据类型、成员谓词、非成员谓词、导入、字段、模块、别名
私有注释用于防止名称被导出。
如果名称具有注释 private,或者通过带 private 注释的 import 语句访问该名称,则只能从当前模块的名称空间中引用该名称。
query
可用于: 非成员谓词、别名
查询注释用于将谓词(或谓词别名)转换为查询。这意味着它是 QL 程序输出的一部分。
编译器语法 Compiler pragmas
下面的编译器准则影响查询的编译和优化。除非遇到严重的性能问题,否则应该避免使用这些注释。
在向代码中添加 pragmas 之前,请联系 GitHub 描述性能问题。这样,我们就可以为您的问题提出最佳解决方案,并在改进 QL 优化器时将其考虑在内。
内联
对于简单谓词,QL 优化器有时用谓词体本身替换对谓词的调用。这就是所谓的内联。
例如,假设有一个定义谓词 one (int i){ i = 1}和一个对该谓词的调用... one (y) ... 。QL 优化器可以将谓词内联到... y = 1... 。
您可以使用下面的编译器杂注注释来控制 QL 优化器内嵌谓词的方式。
pragma[inline]
可用于: 特征谓词、成员谓词和非成员谓词
Pragma [ inline ]注释告诉 QL 优化器始终将带注释的谓词内联到调用它的位置。当一个谓词主体的计算开销非常大时,这可能很有用,因为它确保谓词在被调用的地方与其他上下文信息一起计算。
pragma[noinline]
可用于: 特征谓词、成员谓词和非成员谓词
Pragma [ noinline ]注释用于防止将谓词内联到调用它的位置。在实践中,当您已经在“ helper”谓词中将某些变量组合在一起时,这个注释非常有用,以确保在一块中计算关系。这有助于提高性能。QL 优化器的内联可能会撤消帮助器谓词的工作,因此最好使用 pragma [ noinline ]对其进行注释。
pragma[nomagic]
可用于: 特征谓词、成员谓词和非成员谓词
Pragma [ nomagic ]注释用于防止 QL 优化器在谓词上执行“ magic sets”优化。
这种优化包括从谓词调用的上下文中获取信息并将其推送到谓词体中。这通常是有益的,因此您不应该使用 pragma [ nomagic ]注释,除非 GitHub 建议这样做。
请注意,nomagic 意味着 noinline。
pragma[noopt]
可用于: 特征谓词、成员谓词和非成员谓词
杂注[ noopt ]注释用于防止 QL 优化器优化谓词,除非编译和计算绝对必要。
这很少是必要的,除非 GitHub 建议使用 pragma [ noopt ]注释,否则不应该使用它来帮助解决性能问题。
当你使用这个注释时,要注意以下问题:
-
QL 优化器以一种有效的方式自动排序复杂公式的连接。在 noopt 谓词中,连接词按照编写它们的顺序进行计算。
-
QL 优化器会自动创建中间连接词,将某些公式“转换”为更简单公式的连接。在 noopt 谓词中,必须显式地编写这些连词。特别是,不能在强制转换上链接谓词调用或调用谓词。您必须将它们写成多个连接词,并显式地对它们进行排序。
例如,假设你有以下定义:
class Small extends int { Small() { this in [1 .. 10] } Small getSucc() { result = this + 1} } predicate p(int i) { i.(Small).getSucc() = 2 } predicate q(Small s) { s.getSucc().getSucc() = 3 }
如果您添加了 noopt pragmas,则必须重写谓词。例如:
pragma[noopt] predicate p(int i) { exists(Small s | s = i and s.getSucc() = 2) } pragma[noopt] predicate q(Small s) { exists(Small succ | succ = s.getSucc() and succ.getSucc() = 3 ) }
pragma[only_bind_out]
可用于: 表达式
Pragma [ only _ bind _ out ]注释允许您指定 QL 编译器应该绑定表达式的方向。在 QL 优化器以低效率的方式订购 QL 程序的某些部分的情况下,这对于提高性能非常有用。
例如,x = pragma only _ bind _ out 在语义上等价于 x = y,但具有不同的绑定行为。X = y 从 y 绑定 x,反之亦然,而 x = pragma only _ bind _ out 只绑定 x 从 y。
有关更多信息,请参见“绑定”
pragma[only_bind_into]
可用于: 表达式
Pragma [ only _ bind _ into ]注释允许您指定 QL 编译器应该绑定表达式的方向。在 QL 优化器以低效率的方式订购 QL 程序的某些部分的情况下,这对于提高性能非常有用。
例如,x = pragma only _ bind _ into 在语义上等价于 x = y,但具有不同的绑定行为。X = y 从 y 绑定 x,反之亦然,而 x = pragma only _ bind _ into 只从 x 绑定 y。
有关更多信息,请参见“绑定”
语言标准
可用于: 类、特征谓词、成员谓词和非成员谓词
language[monotonicAggregates]
这个注释允许您使用单调聚合而不是标准的 QL 聚合。
有关更多信息,请参见“单调聚合”
装订集
可用于: 类、特征谓词、成员谓词和非成员谓词
bindingset[...]
可以使用此注释显式地声明谓词或类的绑定集。绑定集是谓词或类主体参数的一个子集,这样,如果这些参数被约束到一个有限的值集,那么谓词或类本身是有限的(也就是说,它计算为一个有限的元组集)。
Bindingset 注释接受一个以逗号分隔的变量列表。
- 当您对谓词进行注释时,每个变量必须是谓词的参数,可能包括 (用于特征谓词和成员谓词)和 (用于返回结果的谓词) 。」
- 在注释一个类时,每个变量必须是 或者是课堂上的一个场。CodeQL CLI 的2.3.0版本和 LGTM Enterprise 的1.26版本都支持类的绑定集