Go 代码基本查询¶
了解如何使用 VS Code 和 CodeQL 扩展编写和运行简单的 CodeQL 查询。
有关为 VS Code 安装 CodeQL 扩展的信息,请参阅“为 VS Code 安装 CodeQL”。
关于查询¶
我们将运行的查询搜索代码中定义在值类型上的方法,这些方法通过写入字段来修改其接收器。
func (s MyStruct) valueMethod() { s.f = 1 } // method on value
这是有问题的,因为接收器参数是按值传递,而不是按引用传递。因此,valueMethod 以接收器对象的副本调用,因此它对接收器所做的任何更改对调用者都不可见。为了防止这种情况,应在指针上定义该方法。
func (s *MyStruct) pointerMethod() { s.f = 1 } // method on pointer
有关在 Go 中使用值或指针上的方法的更多信息,请参阅 Go 常见问题解答。
查找 CodeQL 数据库进行实验¶
在开始为 Go 代码编写查询之前,您需要一个 CodeQL 数据库来运行这些查询。最简单的方法是从 GitHub.com 直接下载使用 Go 的仓库的数据库。
- 在 VS Code 中,单击左侧边栏中的 **QL** 图标
,以显示 CodeQL 扩展。
- 单击 CodeQL 扩展顶部的 **从 GitHub 下载** 或 GitHub 徽标
,以打开一个输入字段。
- 将仓库的 URL 复制到该字段中,然后按键盘 **Enter** 键。例如,https://github.com/go-gorm/gorm。
- 可选:如果仓库有多个 CodeQL 数据库可用,请选择
go
以下载从 Go 代码创建的数据库。
有关数据库下载进度的信息将显示在 VS Code 的右下角。下载完成后,该数据库将在 CodeQL 扩展的 **数据库** 部分中显示一个复选标记(请参阅下面的屏幕截图)。
运行快速查询¶
VS Code 的 CodeQL 扩展在命令面板中添加了多个 **CodeQL: ** 命令,包括 **快速查询**,您可以使用它在没有任何设置的情况下运行查询。
从 VS Code 的命令面板中,选择 **CodeQL: 快速查询**。
片刻之后,将打开一个新的选项卡 *quick-query.ql*,您可以为当前选择的 CodeQL 数据库(此处为
go
数据库)编写查询。如果系统提示您将工作区重新加载为多文件夹工作区以允许快速查询,请接受或使用入门工作流创建一个新的工作区。
在快速查询选项卡中,删除
select ""
,并将以下查询粘贴到导入语句import go
下方。from Method m, Variable recv, Write w, Field f where recv = m.getReceiver() and w.writesField(recv.getARead(), f, _) and not recv.getType() instanceof PointerType select w, "This update to " + f + " has no effect, because " + recv + " is not a pointer."
将查询保存到其默认位置(工作区的临时“快速查询”目录,位于
GitHub.vscode-codeql/quick-queries
下)。在查询选项卡中右键单击,然后选择 **CodeQL: 在所选数据库上运行查询**。(或者,从命令面板运行命令。)
该查询将需要几分钟才能返回结果。查询完成后,结果将显示在 CodeQL 查询结果视图中,位于主编辑器视图旁边。
查询结果列在两列中,对应于查询的
select
子句中的表达式。第一列对应于w
,它是源代码中修改接收器recv
的位置。第二列是警报消息。

如果找到任何匹配的代码,请单击 w
列中的链接以打开文件并突出显示匹配的位置。

注意
如果要将实验查询移动到更持久的位置,则需要移动整个
Quick Queries
目录。该目录是一个 CodeQL 包,包含一个qlpack.yml
文件,该文件将内容定义为针对 Go CodeQL 数据库的查询。有关 CodeQL 包的更多信息,请参阅“管理 CodeQL 查询包和库包”。
关于查询结构¶
在初始 import
语句之后,这个简单的查询包含三个部分,这些部分与 SQL 查询的 FROM、WHERE 和 SELECT 部分具有相似的用途。
查询部分 | 用途 | 详细信息 |
---|---|---|
import go |
导入 Go 的标准 CodeQL 库。 | 每个查询都以一个或多个 import 语句开头。 |
from Method m, Variable recv, Write w, Field f |
定义查询的变量。声明采用以下形式:<type> <variable name> |
我们声明
|
where recv = m.getReceiver() and w.writesField(recv.getARead(), f, _) and not recv.getType() instanceof PointerType |
定义对变量的条件。 |
|
select w, "This update to " + f + " has no effect, because " + recv + " is not a pointer." |
定义每个匹配项要报告的内容。
|
报告 w ,并显示一条消息来解释潜在的问题。 |
扩展查询¶
查询编写是一个固有的迭代过程。您可以编写一个简单的查询,然后在运行它时,您可以发现以前没有考虑到的示例,或者改进的机会。
删除误报结果¶
在该查询的第一次迭代生成的結果中,您可以找到调用值方法但返回接收器变量的情况。在这种情况下,对接收器的更改对调用者并不不可见,因此不需要指针方法。这些是误报结果,您可以通过添加一个额外的条件来删除这些结果来改进查询。
要排除这些值
将 where 子句扩展为包含以下额外条件
not exists(ReturnStmt ret | ret.getExpr() = recv.getARead().asExpr())
现在,
where
子句为where e.isPure() and recv = m.getReceiver() and w.writesField(recv.getARead(), f, _) and not recv.getType() instanceof PointerType and not exists(ReturnStmt ret | ret.getExpr() = recv.getARead().asExpr())
重新运行查询。
现在结果更少了,因为不再报告返回接收器变量的值方法。