注释¶
注释是在 QL 实体或名称声明之前直接放置的字符串。
例如,要将模块 M
声明为私有,您可以使用
private module M {
...
}
- 请注意,一些注释作用于实体本身,而另一些则作用于实体的特定名称
- 作用于实体:
abstract
、cached
、external
、transient
、override
、pragma
、language
和bindingset
- 作用于名称:
deprecated
、library
、private
、final
和query
- 作用于实体:
例如,如果您使用 private
注释实体,则只有该特定名称是私有的。您仍然可以使用其他名称(使用 别名)访问该实体。另一方面,如果您使用 cached
注释实体,则实体本身将被缓存。
以下是一个明确的示例
module M {
private int foo() { result = 1 }
predicate bar = foo/0;
}
在这种情况下,查询 select M::foo()
会导致编译器错误,因为名称 foo
是私有的。查询 select M::bar()
是有效的(结果为 1
),因为名称 bar
是可见的,它是谓词 foo
的别名。
您可以将 cached
应用于 foo
,但不能应用于 bar
,因为 foo
是实体的声明。
注释概述¶
本节介绍了不同注释的作用以及何时可以使用它们。您还可以在 QL 语言规范 的“注释”部分找到一个摘要表。
abstract
¶
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
¶
适用于: 类、代数数据类型、特征谓词、成员谓词、非成员谓词、模块
cached
注释指示应完整评估实体并将其存储在评估缓存中。所有对该实体的后续引用都将使用已计算的数据。这会影响来自其他查询的引用以及来自当前查询的引用。
例如,缓存评估时间很长且在许多地方重复使用的谓词可能很有用。
您应该谨慎使用 cached
,因为它可能会产生意想不到的后果。例如,缓存的谓词可能会占用大量存储空间,并可能阻止 QL 编译器根据每个使用位置的上下文对谓词进行优化。但是,对于只需要计算一次谓词的权衡来说,这可能是合理的。
如果您使用 cached
注释类或模块,则其主体中的所有非 私有 实体也必须使用 cached
注释,否则编译器会报告错误。
deprecated
¶
适用于: 类、代数数据类型、成员谓词、非成员谓词、导入、字段、模块、别名
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 注释。
transient
¶
适用于: 非成员谓词
transient
注释应用于也使用 external
注释的非成员谓词,以指示在评估期间不应将其缓存到磁盘。注意,如果您尝试在没有 external
的情况下应用 transient
,编译器将报告错误。
final
¶
final
注释应用于不能被重写或扩展的名称。换句话说,最终类或最终类型别名不能作为任何其他类型的基类型,最终谓词或字段不能在子类中被重写。
如果您不希望子类更改特定实体的含义,这很有用。
例如,谓词 hasName(string name)
在元素具有名称 name
时成立。它使用谓词 getName()
来检查这一点,并且子类更改此定义没有意义。在这种情况下,hasName
应该是最终的
class Element ... {
string getName() { result = ... }
final predicate hasName(string name) { name = this.getName() }
}
library
¶
适用于: 类
重要
此注释已弃用。与其使用
library
注释名称,不如将其放在私有(或私有导入)模块中。
library
注释应用于只能从 .qll
文件中引用的名称。如果您尝试从没有 .qll
扩展名的文件中引用该名称,则 QL 编译器会返回错误。
private
¶
适用于: 类、代数数据类型、成员谓词、非成员谓词、导入、字段、模块、别名
使用 private
注释来阻止名称被导出。
如果某个名称带有 private
注释,或者它通过带有 private
注释的导入语句访问,那么您只能从当前模块的 命名空间 中引用该名称。
编译器pragma¶
以下编译器pragma会影响查询的编译和优化。除非您遇到严重的性能问题,否则应避免使用这些注释。
在将pragma添加到代码之前,请联系 GitHub 描述性能问题。这样我们就可以为您提供针对您问题的最佳解决方案,并在改进 QL 优化器时将其考虑在内。
内联¶
对于简单的谓词,QL 优化器有时会用谓词体本身替换对谓词的 调用。这被称为内联。
例如,假设您有一个定义 predicate one(int i) { i = 1 }
,并调用了该谓词 ... one(y) ...
。QL 优化器可能会将谓词内联到 ... y = 1 ...
。
您可以使用以下编译器pragma注释来控制 QL 优化器内联谓词的方式。
pragma[inline]
¶
使用 pragma[inline]
注释告诉 QL 优化器始终将带注释的谓词内联到调用它的位置。当谓词体非常昂贵,需要完全计算时,这可能很有用,因为它可以确保在调用谓词的位置,使用其他上下文信息对其进行评估。
pragma[inline_late]
¶
必须将 pragma[inline_late]
注释与 bindingset[...]
pragma 一起使用。它们一起告诉 QL 优化器使用指定的绑定集来评估带注释谓词体以及调用位置的连接顺序,并在连接排序后将谓词体内联到调用位置。这对于防止优化器选择次优的连接顺序可能很有用。
例如,在下面的示例中,pragma[inline_late]
和 bindingset[x]
注释指定在 x
已绑定的上下文中,对 p
的调用应该按照连接顺序进行。这将强制连接排序器在 p(x)
之前排序 q(x)
,这比在 q(x)
之前排序 p(x)
更有效率。
bindingset[x]
pragma[inline_late]
predicate p(int x) { x in [0..100000000] }
predicate q(int x) { x in [0..10000] }
from int x
where p(x) and q(x)
select x
pragma[noinline]
¶
使用 pragma[noinline]
注释来阻止谓词内联到调用它的位置。在实践中,当您已经在“辅助”谓词中将某些变量组合在一起时,该注释很有用,以确保关系作为一个整体进行评估。这有助于提高性能。QL 优化器的内联可能会撤消辅助谓词的工作,因此最好用 pragma[noinline]
为其添加注释。
pragma[nomagic]
¶
使用 pragma[nomagic]
注释来阻止 QL 优化器对谓词执行“神奇集合”优化。
这种优化涉及从谓词 调用 的上下文中获取信息,并将其推入谓词体中。这通常是有益的,因此您不应使用 pragma[nomagic]
注释,除非 GitHub 建议您这样做。
请注意,nomagic
意味着 noinline
。
pragma[noopt]
¶
使用 pragma[noopt]
注释来阻止 QL 优化器优化谓词,除非为了编译和评估正常工作而绝对必要。
这很少必要,您不应使用 pragma[noopt]
注释,除非 GitHub 建议您这样做,例如,帮助解决性能问题。
当您使用此注释时,请注意以下问题
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
pragma,您必须重写谓词。例如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](y)
在语义上等同于 x = y
,但具有不同的绑定行为。 x = y
从 y
绑定 x
,反之亦然,而 x = pragma[only_bind_out](y)
只从 y
绑定 x
。
有关更多信息,请参阅“绑定”。