CodeQL 文档

在 CodeQL 查询中提供位置

CodeQL 包含用于提取代码库中元素位置的机制。在编写自定义 CodeQL 查询和库时使用这些机制,以帮助向用户显示信息。

关于位置

在向用户显示信息时,应用程序需要能够从查询结果中提取位置信息。为了做到这一点,所有可以提供位置信息的 QL 类都应该通过以下机制之一来做到这一点。

此列表按优先级排序,以便使用第一个可用的机制。

注意

由于 QL 是一种关系语言,因此没有任何东西可以强制执行 QL 类中的每个实体都映射到精确的一个位置。这是库设计者(或在以下第三种选项的情况下是提取器)的责任。如果实体根本没有分配任何位置,用户将无法从查询结果点击到源代码查看器。如果分配了多个位置,结果可能会重复。

提供 URL

可以通过定义一个返回 string 的 QL 谓词来提供自定义 URL,其名称为 getURL - 请注意,大小写很重要,并且不允许使用参数。例如

class JiraIssue extends ExternalData {
    JiraIssue() {
        getDataPath() = "JiraIssues.csv"
    }

    string getKey() {
        result = getField(0)
    }

    string getURL() {
        result = "http://mycompany.com/jira/" + getKey()
    }
}

文件 URL

Visual Studio Code 的 CodeQL 扩展和 GitHub 中的代码扫描视图支持显示定义源文件中行和列的 URL。

模式为 file://,后面跟着文件的绝对路径,再跟着四个用冒号分隔的数字。数字表示起始行、起始列、结束行和结束列。行号和列号都是**从 1 开始**,例如

  • file://opt/src/my/file.java:0:0:0:0 用于链接到整个文件。
  • file:///opt/src/my/file.java:1:1:2:1 表示从文件开头开始并扩展到第二行第一个字符的位置(范围是包含的)。
  • file:///opt/src/my/file.java:1:0:1:0 按惯例,表示文件的整个第一行。

按照惯例,整个文件的地址也可以用没有尾随数字的 file:// URL 来表示。可选地,文件中的位置可以使用三个数字来表示,分别定义位置的起始行号、字符偏移量和字符长度。这些类型的结果不会显示为代码扫描警报。

其他类型的 URL

以下不太常见的 URL 类型是有效的,但不会被解释为代码扫描警报,并且将从任何结果中省略

  • **HTTP URL** 在某些客户端应用程序中受支持。例如,请参阅上面的代码片段。
  • **文件夹 URL** 可能有用,例如,提供文件夹级别的指标。它们可以使用文件 URL,例如 file:///opt/src:0:0:0:0,但它们也可以以 folder:// 的方案开头,并且没有尾随数字,例如 folder:///opt/src
  • **相对文件 URL** 类似于普通文件 URL,但以 relative:// 的方案开头。它们通常只在特定数据库的上下文中才有意义,并且被认为是隐式地以数据库的源位置为前缀。请注意,特别是,文件的相对 URL 将保持不变,无论数据库在何处进行分析。将这些 URL 作为输入在导入外部信息时通常最方便;从 QL 类中选择一个是不寻常的,并且客户端应用程序可能无法适当地处理它。

提供位置信息

如果没有定义 getURL() 成员谓词,则将检查 QL 类是否存在名为 hasLocationInfo(..) 的成员谓词。这可以被理解为一种在不使用 QL 构造长 URL 字符串的情况下提供文件 URL(见上文)的便捷方法。 hasLocationInfo(..) 应该是一个谓词,它的第一列必须是 string 类型的(它对应于文件 URL 的“路径”部分),并且它必须有另外 3 或 4 个 int 类型的列,这些列被解释为文件 URL 上的尾随三或四个数字组。

例如,让我们假设提取器提供的用于方法的位置从方法名的第一个字符扩展到方法体的结束大括号,并且我们希望“修复”它们以确保只选择方法名。以下代码显示了两种实现此目标的方法

class MyMethod extends Method {
    // The locations from the database, which we want to modify.
    Location getLocation() { result = super.getLocation() }

    /* First member predicate: Construct a URL for the desired location. */
    string getURL() {
        exists(Location loc | loc = this.getLocation() |
            result = "file://" + loc.getFile().getFullName() +
                ":" + loc.getStartLine() +
                ":" + loc.getStartColumn() +
                ":" + loc.getStartLine() +
                ":" + (loc.getStartColumn() + getName().length() - 1)
        )
    }

    /* Second member predicate: Define hasLocationInfo. This will be more
       efficient (it avoids constructing long strings), and will
       only be used if getURL() is not defined. */
    predicate hasLocationInfo(string path, int sl, int sc, int el, int ec) {
        exists(Location loc | loc = this.getLocation() |
            path = loc.getFile().getFullName() and
            sl = loc.getStartLine() and
            sc = loc.getStartColumn() and
            el = sl and
            ec = sc + getName().length() - 1
        )
    }
}

使用提取的位置信息

最后,如果以上两个谓词失败,客户端应用程序将尝试调用一个名为 getLocation() 的谓词,不带参数,并尝试将以上两个谓词之一应用于结果。这允许将某些位置放入数据库、分配标识符并拾取。

按照惯例, getLocation() 谓词的返回值应该是一个名为 Location 的类,并且它应该定义 hasLocationInfo(..)(或 getURL(),尽管前者更可取)的一个版本。如果 Location 类没有提供这两个成员谓词中的任何一个,那么将没有位置信息可用。

toString() 谓词

除了扩展基本类型的类之外,所有类都必须提供一个 string toString() 成员谓词。如果您不提供,查询编译器会报错。上面提到的关于位置的唯一性警告也适用于这里。

进一步阅读

  • ©GitHub, Inc.
  • 条款
  • 隐私