CodeQL 文档

JavaScript 的 CodeQL 库

在分析 JavaScript 程序时,您可以使用 JavaScript 的 CodeQL 库中的大量类。

概述

有一个用于分析 JavaScript 代码的扩展 CodeQL 库。该库中的类以面向对象的形式展示了 CodeQL 数据库中的数据,并提供了抽象和谓词来帮助您完成常见的分析任务。

该库实现为一组 QL 模块,即扩展名为 .qll 的文件。模块 javascript.qll 导入大多数其他标准库模块,因此您可以通过以下方式包含整个库:在您的查询开头加上

import javascript

本教程的其余部分简要概述了该库提供的最重要的类和谓词,包括对 详细 API 文档 的引用(如果适用)。

介绍库

JavaScript 的 CodeQL 库以不同的级别展示有关 JavaScript 源代码的信息

  • 文本 — 表示源代码为非结构化文本文件的类
  • 词法 — 表示源代码为一系列标记和注释的类
  • 语法 — 表示源代码为抽象语法树的类
  • 名称绑定 — 表示作用域和变量的类
  • 控制流 — 表示执行期间控制流的类
  • 数据流 — 用于分析 JavaScript 源代码中的数据流的类
  • 类型推断 — 用于近似 JavaScript 表达式和变量类型的类
  • 调用图 — 表示函数之间的调用者-被调用者关系的类
  • 跨过程数据流 — 用于定义跨过程数据流和污点跟踪分析的类
  • 框架 — 表示对 JavaScript 工具和框架具有特殊意义的源代码实体的类

请注意,文本级别以上(例如词法表示或流图)的表示形式仅适用于不包含致命语法错误的 JavaScript 代码。对于包含此类错误的代码,唯一可用的信息是在文本级别,以及有关错误本身的信息。

此外,该库还支持处理 HTML 文档、JSON 和 YAML 数据、JSDoc 注释以及正则表达式。

文本级别

在最基本的级别上,JavaScript 代码库可以简单地视为组织成文件夹的文件集合,其中每个文件都由零个或多个文本行组成。

请注意,除非您在提取期间明确请求,否则程序的文本内容不会包含在 CodeQL 数据库中。

文件和文件夹

在 CodeQL 库中,文件表示为 File 类的实体,文件夹表示为 Folder 类的实体,它们都是 Container 类的子类。

Container 类提供以下成员谓词

  • Container.getParentContainer() 返回文件或文件夹的父文件夹。
  • Container.getAFile() 返回文件夹中的一个文件。
  • Container.getAFolder() 返回文件夹中嵌套的一个文件夹。

请注意,虽然 getAFilegetAFolderContainer 类上声明,但它们目前仅对 Folder 有结果。

文件和文件夹都有路径,可以通过谓词 Container.getAbsolutePath() 访问。例如,如果 f 表示路径为 /home/user/project/src/index.js 的文件,则 f.getAbsolutePath() 评估为字符串 "/home/user/project/src/index.js",而 f.getParentContainer().getAbsolutePath() 返回 "/home/user/project/src"

这些路径是绝对文件系统路径。如果您想获取相对于 CodeQL 数据库中源位置的文件路径,请改用 Container.getRelativePath()。但是请注意,数据库可能包含未位于源位置之下的文件;对于此类文件,getRelativePath() 不会返回任何内容。

Container 类的以下成员谓词提供了有关文件或文件夹名称的更多信息

  • Container.getBaseName() 返回文件或文件夹的基名称,不包括其父文件夹,但包括其扩展名。在上面的示例中,f.getBaseName() 将返回字符串 "index.js"
  • Container.getStem()Container.getBaseName() 类似,但它包含文件扩展名;因此 f.getStem() 返回 "index"
  • Container.getExtension() 返回文件扩展名,不包括点;因此 f.getExtension() 返回 "js"

例如,以下查询为每个文件夹计算文件夹中包含的 JavaScript 文件(即扩展名为 js 的文件)的数量

import javascript

from Folder d
select d.getRelativePath(), count(File f | f = d.getAFile() and f.getExtension() = "js")

在大多数项目上运行该查询时,结果将包括包含扩展名为 js 的文件的文件夹和不包含此类文件的文件夹。

位置

CodeQL 数据库中的大多数实体都与一个关联的源位置相关联。位置由五部分信息标识:一个文件、一个开始行、一个开始列、一个结束行和一个结束列。行号和列号为 1 为基(因此文件的第一个字符位于第 1 行,第 1 列),结束位置为包含位置。

所有与源位置相关联的实体都属于 Locatable 类。位置本身由 Location 类建模,可以通过成员谓词 Locatable.getLocation() 访问。 Location 类提供以下成员谓词

  • Location.getFile()Location.getStartLine()Location.getStartColumn()Location.getEndLine()Location.getEndColumn() 返回有关位置的详细信息。
  • Location.getNumLines() 返回位置覆盖的(完整或部分)行数。
  • Location.startsBefore(Location)Location.endsAfter(Location) 确定一个位置是否在另一个位置之前开始或之后结束。
  • Location.contains(Location) 指示一个位置是否完全包含另一个位置;l1.contains(l2) 当且仅当 l1.startsBefore(l2)l1.endsAfter(l2) 成立时才成立。

文件中的一行文本由 Line 类表示。该类提供以下成员谓词

  • Line.getText() 返回该行的文本,不包括任何终止换行符。
  • Line.getTerminator() 返回该行的终止符。文件中最后一行可能没有终止符,在这种情况下,此谓词不会返回任何内容;否则,它将返回两个字符的字符串 "\r\n"(回车符后跟换行符),或以下之一字符字符串 "\n"(换行符)、"\r"(回车符)、"\u2028"(Unicode 字符行分隔符)、"\u2029"(Unicode 字符段分隔符)。

请注意,如上所述,程序的文本表示形式不会默认包含在 CodeQL 数据库中。

词法级别

通过 Token 类和 Comment 类,可以提供对 JavaScript 程序的更结构化的视图,它们分别表示标记和注释。

标记

Token 类的最重要的成员谓词如下

  • Token.getValue() 返回标记的源文本。
  • Token.getIndex() 返回标记在其包含脚本中的索引。
  • Token.getNextToken()Token.getPreviousToken() 在标记之间导航。

Token 类有九个子类,每个子类代表一种特定类型的标记。

例如,考虑以下完全在词法级别上运行的查询,该查询查找由数组表达式中省略的元素引起的连续逗号标记

import javascript

class CommaToken extends PunctuatorToken {
    CommaToken() {
        getValue() = ","
    }
}

from CommaToken comma
where comma.getNextToken() instanceof CommaToken
select comma, "Omitted array elements are bad style."

如果查询没有返回结果,则此模式不会在您分析的项目中使用。

您可以使用谓词 Locatable.getFirstToken()Locatable.getLastToken() 访问属于具有源位置的元素的第一个和最后一个标记(如果有)。

注释

Comment 及其子类代表 JavaScript 程序中可能出现的不同类型的注释。

最重要的成员谓词如下

  • Comment.getText() 返回注释的源文本,不包括分隔符。
  • Comment.getLine(i) 返回注释中第 i 行文本(从 0 开始)。
  • Comment.getNumLines() 返回注释中的行数。
  • Comment.getNextToken() 返回紧跟注释的标记。请注意,此类标记始终存在:如果注释出现在文件末尾,则其后续标记是 EOFToken

例如,考虑以下仅使用词法信息的查询,用于查找 HTML 注释,这些注释不是标准的 ECMAScript 功能,应避免使用

import javascript

from HtmlLineComment c
select c, "Do not use HTML comments."

语法级别

JavaScript 库中的大多数类都与将 JavaScript 程序表示为 抽象语法树 (AST) 的集合有关。

ASTNode 包含代表抽象语法树中节点的所有实体,并定义通用树遍历谓词

  • ASTNode.getChild(i): 返回此 AST 节点的第 i 个子节点。
  • ASTNode.getAChild(): 返回此 AST 节点的任何子节点。
  • ASTNode.getParent(): 返回此 AST 节点的父节点(如果有)。

注意

这些谓词仅应用于执行通用 AST 遍历。要访问特定 AST 节点类型的子节点,应使用下面介绍的专用谓词。特别是,查询不应依赖于子节点相对于其父节点的数字索引:这些被视为实现细节,可能会在库的不同版本之间发生变化。

顶层

从语法的角度来看,每个 JavaScript 程序都由一个或多个顶层代码块(简称顶层)组成,这些代码块是不属于更大代码块的 JavaScript 代码块。顶层由类 TopLevel 及其子类表示

每个 TopLevel 类都包含在 File 类中,但单个 File 可能包含多个 TopLevel。要从 TopLevel tl 转到其 File,请使用 tl.getFile();反之,对于 File f,谓词 f.getATopLevel() 返回包含在 f 中的顶层。对于每个 AST 节点,谓词 ASTNode.getTopLevel() 可用于查找它所属的顶层。

TopLevel 还提供以下成员谓词

  • TopLevel.getNumberOfLines() 返回顶层中总行数(包括代码、注释和空格)。
  • TopLevel.getNumberOfLinesOfCode() 返回代码行数,即包含至少一个标记的行。
  • TopLevel.getNumberOfLinesOfComments() 返回包含或属于注释的行数。
  • TopLevel.isMinified() 使用基于每行语句的平均数量的启发式方法来确定顶层是否包含最小化代码。

注意

默认情况下,GitHub 代码扫描会过滤掉最小化顶层中的警报,因为它们通常难以解释。当您在 Visual Studio Code 中编写自己的查询时,不会自动执行此过滤,因此您可能希望在查询中显式添加类似 and not e.getTopLevel().isMinified() 的条件来排除最小化代码中的结果。

语句和表达式

除了 TopLevel 之外,ASTNode 的最重要的子类是 StmtExpr,它们与其子类一起分别代表语句和表达式。本节简要讨论了一些更重要的类和谓词。有关 StmtExpr 的所有子类及其 API 的完整参考,请参阅 Stmt.qllExpr.qll

  • Stmt: 使用 Stmt.getContainer() 访问包含语句的最内层函数或顶层。
    • ControlStmt: 控制其他语句执行的语句,即条件、循环、trywith 语句;使用 ControlStmt.getAControlledStmt() 访问它控制的语句。
      • IfStmt: 一个 if 语句;使用 IfStmt.getCondition()IfStmt.getThen()IfStmt.getElse() 分别访问其条件表达式、"then" 分支和 "else" 分支。
      • LoopStmt: 一个循环;使用 Loop.getBody()Loop.getTest() 分别访问其循环体和其测试表达式。
        • WhileStmtDoWhileStmt: 分别为 "while" 或 "do-while" 循环。
        • ForStmt: 一个 "for" 语句;使用 ForStmt.getInit()ForStmt.getUpdate() 分别访问初始化表达式和更新表达式。
        • EnhancedForLoop: 一个 "for-in" 或 "for-of" 循环;使用 EnhancedForLoop.getIterator() 访问循环迭代器(可以是表达式或变量声明),使用 EnhancedForLoop.getIterationDomain() 访问正在迭代的表达式。
      • WithStmt: 一个 "with" 语句;使用 WithStmt.getExpr()WithStmt.getBody() 分别访问控制表达式和 "with" 语句的语句体。
      • SwitchStmt: 一个 switch 语句;使用 SwitchStmt.getExpr() 访问语句切换的表达式;使用 SwitchStmt.getCase(int)SwitchStmt.getACase() 访问单个 switch case;每个 case 都由 Case 类的实体建模,其成员谓词 Case.getExpr()Case.getBodyStmt(int) 提供对 switch case 检查的表达式(对于 default 未定义)及其语句体的访问。
      • TryStmt: 一个 "try" 语句;使用 TryStmt.getBody()TryStmt.getCatchClause()TryStmt.getFinally 分别访问其语句体、"catch" 子句和 "finally" 块。
    • BlockStmt: 一个语句块;使用 BlockStmt.getStmt(int) 访问块中的单个语句。
    • ExprStmt: 一个表达式语句;使用 ExprStmt.getExpr() 访问表达式本身。
    • JumpStmt: 一个破坏结构化控制流的语句,即 breakcontinuereturnthrow 中的一种;使用谓词 JumpStmt.getTarget() 确定跳转的目标,该目标可以是语句,也可以是(对于 return 和未捕获的 throw 语句)封闭函数。
      • BreakStmt: 一个 "break" 语句;使用 BreakStmt.getLabel() 访问其(可选)目标标签。
      • ContinueStmt: 一个 "continue" 语句;使用 ContinueStmt.getLabel() 访问其(可选)目标标签。
      • ReturnStmt: 一个 "return" 语句;使用 ReturnStmt.getExpr() 访问其(可选)结果表达式。
      • ThrowStmt: 一个 "throw" 语句;使用 ThrowStmt.getExpr() 访问其抛出的表达式。
    • FunctionDeclStmt: 一个函数声明语句;有关可用成员谓词,请参见下文。
    • ClassDeclStmt: 一个类声明语句;有关可用成员谓词,请参见下文。
    • DeclStmt: 一个声明语句,包含一个或多个声明符,可以通过谓词 DeclStmt.getDeclarator(int) 访问。
  • Expr: 使用 Expr.getEnclosingStmt() 获取此表达式所属的最内层语句;Expr.isPure() 确定表达式是否无副作用。
    • Identifier: 一个标识符;使用 Identifier.getName() 获取其名称。
    • Literal: 一个字面量值;使用 Literal.getValue() 获取其值的字符串表示形式,使用 Literal.getRawValue() 获取其原始源文本(包括字符串字面量的周围引号)。
    • ThisExpr: 一个 "this" 表达式。
    • SuperExpr: 一个 "super" 表达式。
    • ArrayExpr: 一个数组表达式;使用 ArrayExpr.getElement(i) 获取第 i 个元素表达式,使用 ArrayExpr.elementIsOmitted(i) 检查第 i 个元素是否被省略。
    • ObjectExpr: 一个对象表达式;使用 ObjectExpr.getProperty(i) 获取对象表达式中的第 i 个属性;属性由 Property 类建模,将在下面详细介绍。
    • FunctionExpr: 一个函数表达式;有关可用成员谓词,请参见下文。
    • ArrowFunctionExpr: 一个 ECMAScript 2015 风格的箭头函数表达式;有关可用成员谓词,请参见下文。
    • ClassExpr: 一个类表达式;有关可用成员谓词,请参见下文。
    • ParExpr: 一个带括号的表达式;使用 ParExpr.getExpression() 获取操作数表达式;对于任何表达式,Expr.stripParens() 可用于递归地剥离任何括号
    • SeqExpr: 由逗号运算符连接的两个或多个表达式的序列;使用 SeqExpr.getOperand(i) 获取第 i 个子表达式。
    • ConditionalExpr: 一个三元条件表达式;成员谓词 ConditionalExpr.getCondition()ConditionalExpr.getConsequent()ConditionalExpr.getAlternate() 分别提供对条件表达式、"then" 表达式和 "else" 表达式的访问。
    • InvokeExpr: 一个函数调用或一个 "new" 表达式;使用 InvokeExpr.getCallee() 获取指定要调用的函数的表达式,使用 InvokeExpr.getArgument(i) 获取第 i 个参数表达式。
      • CallExpr: 一个函数调用。
      • NewExpr: 一个 "new" 表达式。
      • MethodCallExpr: 一个函数调用,其被调用者表达式是属性访问;使用 MethodCallExpr.getReceiver 访问方法调用的接收者表达式,使用 MethodCallExpr.getMethodName() 获取方法名称(如果可以静态确定)。
    • PropAccess: 属性访问,即形式为 e.f 的“点”表达式或形式为 e[p] 的索引表达式;使用 PropAccess.getBase() 获取访问属性的基础表达式(示例中的 e),使用 PropAccess.getPropertyName() 确定访问属性的名称;如果无法静态确定名称,则 getPropertyName() 不返回值。
      • DotExpr: “点”表达式。
      • IndexExpr: 索引表达式(也称为计算属性访问)。
    • UnaryExpr: 一元表达式;使用 UnaryExpr.getOperand() 获取操作数表达式。
    • BinaryExpr: 二元表达式;使用 BinaryExpr.getLeftOperand()BinaryExpr.getRightOperand() 访问操作数表达式。
    • Assignment: 赋值表达式,简单或复合;使用 Assignment.getLhs()Assignment.getRhs() 分别访问左右侧。
    • UpdateExpr: 自增或自减表达式;使用 UpdateExpr.getOperand() 获取操作数表达式。
    • YieldExpr: “yield” 表达式;使用 YieldExpr.getOperand() 访问(可选)操作数表达式;使用 YieldExpr.isDelegating() 检查这是否是一个委托 yield*
    • TemplateLiteral: ECMAScript 2015 模板字面量;TemplateLiteral.getElement(i) 返回模板的第 i 个元素,它可以是插值的表达式,也可以是常量模板元素。
    • TaggedTemplateExpr: ECMAScript 2015 标记模板字面量;使用 TaggedTemplateExpr.getTag() 访问标记表达式,使用 TaggedTemplateExpr.getTemplate() 访问被标记的模板字面量。
    • TemplateElement: 常量模板元素;与字面量一样,使用 TemplateElement.getValue() 获取元素的值,使用 TemplateElement.getRawValue() 获取其原始值。
    • AwaitExpr: “await” 表达式;使用 AwaitExpr.getOperand() 访问操作数表达式。

StmtExpr 共享一个共同的超类 ExprOrStmt,这对于应该对语句或表达式进行操作但不对任何其他 AST 节点进行操作的查询很有用。

作为如何使用表达式 AST 节点的示例,这里有一个查询,它查找形式为 e + f >> g 的表达式;这样的表达式应该改写为 (e + f) >> g 以阐明运算符优先级。

import javascript

from ShiftExpr shift, AddExpr add
where add = shift.getAnOperand()
select add, "This expression should be bracketed to clarify precedence rules."

函数

JavaScript 提供了几种定义函数的方法:在 ECMAScript 5 中,有函数声明语句和函数表达式,而 ECMAScript 2015 添加了箭头函数表达式。这些不同的语法形式分别由类 FunctionDeclStmtStmt 的子类)、FunctionExprExpr 的子类)和 ArrowFunctionExpr(也是 Expr 的子类)表示。所有这三种都是 Function 的子类,它提供了用于访问函数参数或函数体的通用成员谓词。

  • Function.getId() 返回命名函数的 Identifier,它可能未定义函数表达式。
  • Function.getParameter(i)Function.getAParameter() 分别访问第 i 个参数或任何参数;参数由类 Parameter 建模,它是 BindingPattern 的子类(见下文)。
  • Function.getBody() 返回函数体,它通常是一个 Stmt,但对于箭头函数表达式和旧式 表达式闭包 来说,它可能是 Expr

例如,以下查询查找所有表达式闭包

import javascript

from FunctionExpr fe
where fe.getBody() instanceof Expr
select fe, "Use arrow expressions instead of expression closures."

另一个例子,此查询查找具有两个绑定相同变量的参数的函数

import javascript

from Function fun, Parameter p, Parameter q, int i, int j
where p = fun.getParameter(i) and
    q = fun.getParameter(j) and
    i < j and
    p.getAVariable() = q.getAVariable()
select fun, "This function has two parameters that bind the same variable."

类可以通过类声明语句(由 CodeQL 类 ClassDeclStmt 表示,它是 Stmt 的子类)或类表达式(由 CodeQL 类 ClassExpr 表示,它是 Expr 的子类)来定义。这两个类也是 ClassDefinition 的子类,它为访问类的名称、其超类及其主体提供了公共成员谓词

  • ClassDefinition.getIdentifier() 返回命名函数的 Identifier,对于类表达式,它可能未定义。
  • ClassDefinition.getSuperClass() 返回指定超类的 Expr,它可能未定义。
  • ClassDefinition.getMember(n) 返回此类的成员 n 的定义。
  • ClassDefinition.getMethod(n)ClassDefinition.getMember(n) 限制为方法(而不是字段)。
  • ClassDefinition.getField(n)ClassDefinition.getMember(n) 限制为字段(而不是方法)。
  • ClassDefinition.getConstructor() 获取此类的构造函数,可能是合成的默认构造函数。

请注意,类字段还不是标准语言功能,因此其表示形式的详细信息可能会发生变化。

方法定义由类 MethodDefinition 表示,它(与其对应于字段的 FieldDefinition 一样)是 MemberDefinition 的子类。该类提供了以下重要的成员谓词

  • MemberDefinition.isStatic():如果这是一个静态成员,则为真。
  • MemberDefinition.isComputed():如果此成员的名称在运行时计算,则为真。
  • MemberDefinition.getName():如果可以静态确定,则获取此成员的名称。
  • MemberDefinition.getInit():获取此字段的初始化程序;对于方法,初始化程序是一个函数表达式,对于字段,它可能是一个任意表达式,也可能未定义。

有三个类用于对特殊方法建模:ConstructorDefinition 对构造函数进行建模,而 GetterMethodDefinitionSetterMethodDefinition 分别对 getter 和 setter 方法进行建模。

声明和绑定模式

变量通过声明语句(类 DeclStmt)声明,它们有三种形式:var 语句(由类 VarDeclStmt 表示)、const 语句(由类 ConstDeclStmt 表示)和 let 语句(由类 LetStmt 表示)。每个声明语句都有一个或多个声明符,由类 VariableDeclarator 表示。

每个声明符都包含一个绑定模式,由谓词 VariableDeclarator.getBindingPattern() 返回,以及一个可选的初始化表达式,由 VariableDeclarator.getInit() 返回。

通常,绑定模式是一个简单的标识符,例如 var x = 42 中的 x。但是,在 ECMAScript 2015 及更高版本中,它也可以是一个更复杂的解构模式,例如 var [x, y] = arr 中的模式。

各种绑定模式由类 BindingPattern 及其子类表示

  • VarRef:左值位置的简单标识符,例如 var xx = 42 中的 x
  • Parameter:函数或捕获子句参数
  • ArrayPattern:数组模式,例如 [x, y] = arr 的左侧
  • ObjectPattern:对象模式,例如 {x, y: z} = o 的左侧

以下是一个查询示例,用于查找多次声明相同变量的声明语句,不包括缩短代码中的结果

import javascript

from DeclStmt ds, VariableDeclarator d1, VariableDeclarator d2, Variable v, int i, int j
where d1 = ds.getDecl(i) and
    d2 = ds.getDecl(j) and
    i < j and
    v = d1.getBindingPattern().getAVariable() and
    v = d2.getBindingPattern().getAVariable() and
    not ds.getTopLevel().isMinified()
select ds, "Variable " + v.getName() + " is declared both $@ and $@.", d1, "here", d2, "here"

这不是一个常见的问题,因此您可能在自己的项目中找不到任何结果。

请注意此处和接下来的几个查询中使用 not ... isMinified()。这排除了在缩短代码中找到的任何结果。如果您删除 and not ds.getTopLevel().isMinified() 并重新运行查询,将在 *meteor/meteor* 项目的缩短代码中报告两个结果。

属性

对象字面量中的属性由类 Property 表示,它也是 ASTNode 的子类,但既不是 Expr 也不属于 Stmt

Property 有两个子类 ValuePropertyPropertyAccessor,它们分别表示普通值属性和 getter/setter 属性。类 PropertyAccessor 又有两个子类 PropertyGetterPropertySetter,分别表示 getter 和 setter。

谓词 Property.getName()Property.getInit() 提供对定义的属性的名称及其初始值的访问。对于 PropertyAccessor 及其子类,getInit() 被重载以返回 getter/setter 函数。

作为涉及属性的查询的示例,请考虑以下查询,该查询标记包含两个同名属性的对象表达式,不包括缩短代码中的结果

import javascript

from ObjectExpr oe, Property p1, Property p2, int i, int j
where p1 = oe.getProperty(i) and
    p2 = oe.getProperty(j) and
    i < j and
    p1.getName() = p2.getName() and
    not oe.getTopLevel().isMinified()
select oe, "Property " + p1.getName() + " is defined both $@ and $@.", p1, "here", p2, "here"

模块

JavaScript 库支持使用 ECMAScript 2015 模块,以及旧式的 CommonJS 模块(仍然被 Node.js 代码库广泛使用)和 AMD 风格的模块。类 ES2015ModuleNodeModuleAMDModule 代表这三种类型的模块,所有三种都扩展了通用超类 Module

Module 定义的最重要的成员谓词是

  • Module.getName():获取模块的名称,它只是封闭文件的茎(即不带扩展名的基本名称)。
  • Module.getAnImportedModule():获取由该模块导入(通过 importrequire)的另一个模块。
  • Module.getAnExportedSymbol():获取该模块导出的符号的名称。

此外,有一个类 Import 对 ECMAScript 2015 风格的 import 声明和 CommonJS/AMD 风格的 require 调用进行建模;其成员谓词 Import.getImportedModule 提供对导入引用的模块的访问,如果可以静态确定。

名称绑定

名称绑定在 JavaScript 库中使用四个概念进行建模:作用域变量变量声明变量访问,分别由 ScopeVariableVarDeclVarAccess 类表示。

作用域

在 ECMAScript 5 中,有三种作用域:全局作用域(每个程序一个)、函数作用域(每个函数一个)和 catch 子句作用域(每个 catch 子句一个)。这三种作用域分别由 GlobalScopeFunctionScopeCatchScope 类表示。ECMAScript 2015 为 let 绑定变量添加了块作用域,它们也由 Scope 类表示,还有类表达式作用域 (ClassExprScope) 和模块作用域 (ModuleScope)。

Scope 类提供以下 API

  • Scope.getScopeElement() 返回引起此作用域的 AST 节点;对于 GlobalScope 则为未定义。
  • Scope.getOuterScope() 返回此作用域的词法封闭作用域。
  • Scope.getAnInnerScope() 返回词法嵌套在此作用域内的作用域。
  • Scope.getVariable(name)Scope.getAVariable() 返回在此作用域中声明的(隐式或显式)变量。

变量

Variable 类对 JavaScript 程序中的所有变量进行建模,包括全局变量、局部变量和参数(函数和 catch 子句的),无论是否显式声明。

重要的是不要混淆变量及其声明:局部变量可能有多个声明,而全局变量和隐式声明的局部变量 arguments 可能根本没有声明。

变量声明和访问

变量可以通过变量声明符、函数声明语句和表达式、类声明语句或表达式以及函数和 catch 子句的参数进行声明。虽然这些声明的语法形式不同,但在每种情况下,都存在一个标识符来命名声明的变量。我们将该标识符视为声明本身,并将其分配给 VarDecl 类。另一方面,引用变量的标识符被赋予 VarAccess 类。

涉及变量、其声明和其访问的最重要谓词如下

  • Variable.getName()VarDecl.getName()VarAccess.getName() 返回变量的名称。
  • Variable.getScope() 返回变量所属的作用域。
  • Variable.isGlobal()Variable.isLocal()Variable.isParameter() 分别确定变量是全局变量、局部变量还是参数变量。
  • Variable.getAnAccess() 将一个 Variable 映射到所有引用它的 VarAccess
  • Variable.getADeclaration() 将一个 Variable 映射到所有声明它的 VarDecl(可能没有,一个或多个)。
  • Variable.isCaptured() 确定变量是否在词法嵌套在声明它的作用域内的作用域中被访问过。

例如,考虑以下查询,它查找声明相同变量的不同函数声明,即同一作用域内的两个冲突的函数声明(再次排除压缩的代码)

import javascript

from FunctionDeclStmt f, FunctionDeclStmt g
where f != g and f.getVariable() = g.getVariable() and
    not f.getTopLevel().isMinified() and
    not g.getTopLevel().isMinified()
select f, g

一些项目声明了名称相同的冲突函数,并依赖于平台特定的行为来消除这两个声明的歧义。

控制流

CFG.qll 中的类提供了以过程内控制流图 (CFG) 表示的另一种程序表示。

ControlFlowNode 类表示控制流图中的单个节点,它可以是表达式、语句或合成控制流节点。请注意,在 CodeQL 级别,ExprStmt 不会从 ControlFlowNode 继承,尽管它们的实体类型兼容,因此如果需要在基于 AST 的程序表示和基于 CFG 的程序表示之间进行映射,则可以显式地从一个类型转换为另一个类型。

有两种合成控制流节点:入口节点(ControlFlowEntryNode 类),表示顶级或函数的开始,以及退出节点(ControlFlowExitNode 类),表示它们的结束。它们不对应于任何 AST 节点,而只是作为控制流图的唯一入口点和出口点。可以通过 StmtContainer.getEntry()StmtContainer.getExit() 谓词访问入口节点和退出节点。

大多数(但并非全部)顶级和函数都有另一个特殊的 CFG 节点,即起始节点。这是执行开始的 CFG 节点。与作为合成结构的入口节点不同,起始节点对应于实际的程序元素:对于顶级,它是第一个语句的第一个 CFG 节点;对于函数,它是对应于其第一个参数的 CFG 节点,如果没有参数,则是函数体中的第一个 CFG 节点。空顶级没有起始节点。

在大多数情况下,使用起始节点比使用入口节点更好。

控制流图的结构反映在 ControlFlowNode 的成员谓词中

  • ControlFlowNode.getASuccessor() 返回控制流图中此 ControlFlowNode 的后继 ControlFlowNode
  • ControlFlowNode.getAPredecessor()getASuccessor() 的逆运算。
  • ControlFlowNode.isBranch() 确定此节点是否具有多个后继。
  • ControlFlowNode.isJoin() 确定此节点是否具有多个前驱。
  • ControlFlowNode.isStart() 确定此节点是否为起始节点。

许多基于控制流的分析是在 基本块 而不是单个控制流节点方面进行表达的,其中基本块是不包含分支或连接的最大控制流节点序列。来自 BasicBlocks.qllBasicBlock 类表示所有这些基本块。与 ControlFlowNode 类似,它提供成员谓词 getASuccessor()getAPredecessor() 来在基本块级别导航控制流图,并提供成员谓词 getANode()getNode(int)getFirstNode()getLastNode() 来访问基本块内的单个控制流节点。谓词 Function.getEntryBB() 返回函数中的入口基本块,即包含函数入口节点的基本块。类似地,Function.getStartBB() 提供对起始基本块的访问,该基本块包含函数的起始节点。与 CFG 节点一样,getStartBB() 通常比 getEntryBB() 更受欢迎。

作为使用基本块的分析示例,BasicBlock.isLiveAtEntry(v, u) 确定变量 v 是否在给定基本块的入口处 活跃,如果是,则将 u 绑定到 v 的一个使用,该使用引用其在入口处的值。我们可以使用它来查找在函数中使用的全局变量,但它们不活跃(即,对变量的每次读取都在写入之前),这表明该变量应该被声明为局部变量而不是全局变量

import javascript

from Function f, GlobalVariable gv
where gv.getAnAccess().getEnclosingFunction() = f and
    not f.getStartBB().isLiveAtEntry(gv, _)
select f, "This function uses " + gv + " like a local variable."

许多项目都有一些变量看起来像是打算用作局部变量。

数据流

定义和使用

DefUse.qll 提供了类和谓词来确定变量定义和使用的 定义-使用 关系。

VarDefVarUse 分别包含所有定义和使用变量的表达式。对于前者,您可以使用谓词 VarDef.getAVariable() 来找出哪些变量是由给定的变量定义定义的(回想一下,ECMAScript 2015 中的解构赋值同时定义了多个变量)。类似地,谓词 VarUse.getVariable() 返回由变量使用访问的(单个)变量。

定义-使用信息本身由谓词 VarUse.getADef() 提供,该谓词将变量的使用连接到同一变量的定义,其中定义可以到达使用。

例如,以下查询查找未在任何地方使用的局部变量的定义;也就是说,变量在定义之后要么根本没有被引用,要么它的值被覆盖了

import javascript

from VarDef def, LocalVariable v
where v = def.getAVariable() and
    not exists (VarUse use | def = use.getADef())
select def, "Dead store of local variable."

SSA

semmle.javascript.SSA 提供了基于 静态单赋值形式 (SSA) 的程序数据流的更细粒度表示。

在 SSA 形式中,局部变量的每次使用都只有一个 (SSA) 定义到达它。SSA 定义由类 SsaDefinition 表示。它们不是 AST 节点,因为并非每个 SSA 定义都对应于源代码中的显式元素。

总共有五种 SSA 定义

  1. 显式定义 (SsaExplicitDefinition): 它们只是包装一个 VarDef,即像 x = 1 这样的定义显式地出现在源代码中。
  2. 隐式初始化 (SsaImplicitInit): 这些表示在局部变量范围开始时用 undefined 对局部变量进行隐式初始化。
  3. Phi 节点 (SsaPhiNode): 这些是伪定义,在必要时合并两个或多个 SSA 定义;有关说明,请参见上面链接的维基百科页面。
  4. 变量捕获 (SsaVariableCapture): 这些是在代码中出现伪定义,在这些地方捕获变量的值可能会发生变化,而没有显式赋值,例如由于函数调用。
  5. 细化节点 (SsaRefinementNode): 这些是在代码中出现伪定义,在这些地方关于变量的一些内容变得已知;例如,条件 if (x === null) 会在“then”分支的开头引入一个细化节点,记录 x 已知在那里为 null 的事实。(在文献中,这些有时被称为“pi 节点”。)

数据流节点

除了变量定义和使用之外,库 semmle.javascript.dataflow.DataFlow 提供了程序作为数据流图的表示。它的节点是类 DataFlow::Node 的值,它有两个子类 ValueNodeSsaDefinitionNode。前一种节点包装被认为会产生值的表达式或语句(具体来说,是函数或类声明语句,或 TypeScript 命名空间或枚举声明)。后一种节点包装 SSA 定义。

您可以使用谓词 DataFlow::valueNode 将表达式、函数或类转换为其相应的 ValueNode,类似地,DataFlow::ssaDefinitionNode 将 SSA 定义映射到其相应的 SsaDefinitionNode

还有一个辅助谓词 DataFlow::parameterNode,它将参数映射到其对应的数据流节点。(这实际上只是 DataFlow::ssaDefinitionNode 的一个便利包装,因为参数也被认为是 SSA 定义。)

反过来,有一个谓词 ValueNode.getAstNode() 用于从 ValueNodeASTNode 的映射,以及 SsaDefinitionNode.getSsaVariable() 用于从 SsaDefinitionNodeSsaVariable 的映射。还有一个实用程序谓词 Node.asExpr(),它获取 ValueNode 的底层表达式,对于不对应表达式的所有节点来说都是未定义的。(尤其要注意,此谓词对于包装函数或类声明语句的 ValueNode 是未定义的!)

您可以使用谓词 DataFlow::Node.getAPredecessor() 查找可能将值流入此节点的其他数据流节点,以及 getASuccessor 查找另一个方向。

例如,这里有一个查询查找对名为 res 的参数的值调用名为 send 的方法的所有调用,表明它可能正在发送 HTTP 响应

import javascript

from SimpleParameter res, DataFlow::Node resNode, MethodCallExpr send
where res.getName() = "res" and
      resNode = DataFlow::parameterNode(res) and
      resNode.getASuccessor+() = DataFlow::valueNode(send.getReceiver()) and
      send.getMethodName() = "send"
select send

请注意,此库中的数据流建模是过程内的,也就是说,不会对跨函数调用和返回的流进行建模。同样,也不对通过对象属性和全局变量的流进行建模。

类型推断

semmle.javascript.dataflow.TypeInference 为 JavaScript 实现了一个简单的类型推断,该推断基于过程内、堆不敏感的流分析。基本上,推断算法将变量和表达式的可能具体运行时值近似为抽象值的集合(由类 AbstractValue 表示),每个抽象值代表一组具体值。

例如,有一个抽象值代表所有非零数字,另一个代表所有非空字符串,除了可以转换为数字的字符串。这两个抽象值都是相当粗略的近似值,它们代表了非常大量的具体值。

其他抽象值更精确,以至于它们代表单个具体值:例如,有一个抽象值代表具体 null 值,另一个代表数字零。

有一组特殊的抽象值称为不定抽象值,它们代表所有具体值。分析使用这些值来处理它无法推断更精确值的表达式,例如函数参数(如上所述,分析是过程内的,因此不会对参数传递进行建模)或属性读取(分析也不会对属性值进行建模)。

每个不定抽象值都与一个字符串值相关联,该字符串值描述了不精确的原因。在上面的示例中,参数的不定值将具有原因 "call",而属性的不定值将具有原因 "heap"

要检查抽象值是否是不定的,可以使用 isIndefinite 成员谓词。它的单个参数描述了不精确的原因。

每个抽象值都具有一个或多个关联类型(CodeQL 类 InferredType,大致对应于 typeof 运算符计算的类型标记。这些类型是 nullundefinedbooleannumberstringfunctionclassdateobject

要访问类型推断的结果,请使用类 DataFlow::AnalyzedNode:任何 DataFlow::Node 都可以强制转换为该类,此外还有一个方便的谓词 Expr::analyze,它将表达式直接映射到其相应的 AnalyzedNode

获得 AnalyzedNode 后,可以使用谓词 AnalyzedNode.getAValue() 访问为其推断的抽象值,以及 getAType() 获取推断的类型。

例如,这里有一个查询查找对实际上不能为 null 的表达式进行 null 检查

import javascript

from StrictEqualityTest eq, DataFlow::AnalyzedNode nd, NullLiteral null
where eq.hasOperands(nd.asExpr(), null) and
      not nd.getAValue().isIndefinite(_) and
      not nd.getAValue() instanceof AbstractNull
select eq, "Spurious null check."

换句话说,该查询查找其中一个操作数是 null 字面量,另一个操作数是我们将转换为 AnalyzedNode 的表达式的相等测试 eq。如果该节点的类型推断结果是精确的(也就是说,没有推断的值是不定的)并且(null 的抽象表示)不在其中,我们标记 eq

您可以通过定义 DataFlow::AnalyzedNode 的新子类并覆盖 getAValue 来添加自定义类型推断规则。您还可以通过扩展抽象类 CustomAbstractValueTag 来引入新的抽象值,该类是 string 的子类:属于该类的每个字符串都会产生一个类型为 CustomAbstractValue 的相应抽象值。您可以使用谓词 CustomAbstractValue.getTag() 从抽象值映射到其标记。通过实现类 CustomAbstractValueTag 的抽象谓词,您可以定义自定义抽象值的语义,例如它们强制转换为什么原始值以及它们的类型是什么。

调用图

该 JavaScript 库实现了一个简单的调用图构造算法,用于静态地近似函数调用和new表达式的可能调用目标。由于 JavaScript 的动态类型特性及其对高阶函数和反射语言特性的支持,构建静态调用图非常困难。简单的调用图算法往往是不完整的,也就是说,它们经常无法解析所有可能的调用目标。更复杂的算法可能会遇到相反的精度问题,也就是说,它们可能会推断出许多虚假的调用目标。

调用图由类DataFlow::InvokeNode的成员谓词getACallee()表示,该谓词计算给定调用的可能被调用者,即在运行时可能被该表达式调用的函数。

此外,还有三个成员谓词指示此调用被调用者信息的质量

  • DataFlow::InvokeNode.isImprecise():对于调用图构建器可能推断出虚假调用目标的调用有效。
  • DataFlow::InvokeNode.isIncomplete():对于调用图构建器可能无法推断出可能调用目标的调用有效。
  • DataFlow::InvokeNode.isUncertain():如果isImprecise()isIncomplete()有效,则有效。

作为一个基于调用图的查询示例,以下是一个查询,用于查找调用图构建器无法找到任何被调用者的调用,尽管对该调用的分析是完整的

import javascript

from DataFlow::InvokeNode invk
where not invk.isIncomplete() and
      not exists(invk.getACallee())
select invk, "Unable to find a callee for this invocation."

跨过程数据流

到目前为止描述的基于数据流图的分析都是过程内的:它们不考虑从函数参数到参数的流,也不考虑从return到函数调用者的流。数据流库还提供了一个框架,用于构建自定义的跨过程分析。

我们在这里区分数据流本身和污点跟踪:后者不仅考虑值保留流(例如从变量定义到使用),还考虑一个值影响(“污染”)另一个值而不完全确定它的情况。例如,在赋值s2 = s1.substring(i)中,s1的值会影响s2的值,因为s2被分配了s1的子串。一般来说,s2不会被分配s1本身,所以从s1s2没有数据流,但s1仍然污染了s2

我们通常希望根据其(流开始的地方)、(应该跟踪流的地方)以及屏障净化器(流中断的地方)来指定数据流或污点分析。净化器在安全分析中非常常见:例如,跟踪不受信任的用户输入流入(例如)SQL 查询的分析必须跟踪验证输入的代码,从而使其安全使用。这种验证步骤是净化器的示例。

DataFlow::ConfigurationTaintTracking::Configuration分别允许通过覆盖以下谓词来指定数据流或污点分析

  • isSource(DataFlow::Node nd)选择所有流跟踪开始的节点nd
  • isSink(DataFlow::Node nd)选择所有流被跟踪到的节点nd
  • isBarrier(DataFlow::Node nd)选择所有充当数据流屏障的节点ndisSanitizer是污点跟踪配置的相应谓词。
  • isBarrierEdge(DataFlow::Node src, DataFlow::Node trg)isBarrier(nd)的变体,它允许除了屏障节点之外还指定屏障;同样,isSanitizerEdge是污点跟踪的相应谓词;
  • isAdditionalFlowStep(DataFlow::Node src, DataFlow::Node trg)允许为该分析指定自定义的额外流步骤;isAdditionalTaintStep是污点跟踪配置的相应谓词。

由于出于技术原因,两个Configuration类都是string的子类型,因此您必须为每个流配置选择一个唯一的名称,并在特征谓词中将其与this等同起来(如下面的示例所示)。

谓词Configuration.hasFlow执行实际的流跟踪,从源开始,查找流到汇的流,该流不会经过屏障节点或边。

例如,假设我们正在开发一个分析,以查找硬编码密码。我们可能会编写一个简单的查询,查找流入名为"password"的变量的字符串常量。

import javascript

class PasswordTracker extends DataFlow::Configuration {
    PasswordTracker() {
        // unique identifier for this configuration
        this = "PasswordTracker"
    }

    override predicate isSource(DataFlow::Node nd) {
        nd.asExpr() instanceof StringLiteral
    }

    override predicate isSink(DataFlow::Node nd) {
        passwordVarAssign(_, nd)
    }

    predicate passwordVarAssign(Variable v, DataFlow::Node nd) {
       v.getAnAssignedExpr() = nd.asExpr() and
       v.getName().toLowerCase() = "password"
    }
}

现在,我们可以重新表达我们的查询以使用Configuration.hasFlow

from PasswordTracker pt, DataFlow::Node source, DataFlow::Node sink, Variable v
where pt.hasFlow(source, sink) and pt.passwordVarAssign(v, sink)
select sink, "Password variable " + v + " is assigned a constant string."

语法错误

通常无法分析包含语法错误的 JavaScript 代码。对于此类代码,词法和语法表示不可用,因此没有名称绑定信息、调用图或控制和数据流。在这种情况下,唯一可用的是类JSParseError的值,该值表示语法错误。它提供了有关语法错误位置的信息(JSParseErrorLocatable的子类)以及通过谓词JSParseError.getMessage的错误消息。

请注意,对于一些非常简单的语法错误,解析器可以恢复并继续解析。如果发生这种情况,除了表示在解析过程中遇到的(可恢复的)语法错误的JSParseError值之外,词法和语法信息也可用。

框架

AngularJS

semmle.javascript.frameworks.AngularJS提供了对AngularJS(Angular 1.x)代码的支持。它最重要的类是

HTTP 框架库

semmle.javacript.frameworks.HTTP提供了对各种 HTTP 框架的常见概念进行建模的类。

目前支持的框架包括Express、标准 Node.js httphttps 模块、ConnectKoaHapiRestify

最重要的类包括(都在模块HTTP中)

  • ServerDefinition:创建一个新的 HTTP 服务器的表达式。
  • RouteHandler:处理 HTTP 请求的回调。
  • RequestExpr:可能包含 HTTP 请求对象的表达式。
  • ResponseExpr:可能包含 HTTP 响应对象的表达式。
  • HeaderDefinition:设置一个或多个 HTTP 响应头的表达式。
  • CookieDefinition:在 HTTP 响应中设置 cookie 的表达式。
  • RequestInputAccess:访问用户控制的请求数据的表达式。

对于每个框架库,都有一个相应的 CodeQL 库(例如 semmle.javacript.frameworks.Express),它为该框架实例化上述类,并添加特定于框架的类。

Node.js

semmle.javascript.NodeJS通过以下类提供了对Node.js模块的支持

  • NodeModule:定义 Node.js 模块的顶级模块;有关更多信息,请参见关于模块的部分。
  • Require:调用特殊的require函数来导入模块。

作为使用这些类的示例,以下是一个查询,它统计每个模块导入的其他模块的数量

import javascript

from NodeModule m
select m, count(m.getAnImportedModule())

当您分析一个项目时,对于每个模块,您可以看到它导入了多少其他模块。

NPM

semmle.javascript.NPM通过以下类提供了对NPM包的支持

  • PackageJSON:描述 NPM 包的package.json文件;各种 getter 谓词可用于访问有关包的详细信息,这些详细信息在在线 API 文档中进行了描述。
  • BugTrackerInfoContributorInfoRepositoryInfo:这些类对 package.json 文件中的部分进行了建模,这些部分提供了有关错误跟踪系统、贡献者和存储库的信息。
  • PackageDependencies:对 NPM 包的依赖关系进行建模;谓词 PackageDependencies.getADependency(pkg, v)pkg 绑定到名称,将 v 绑定到由 package.json 文件所需的包的版本。
  • NPMPackageFolder 的子类,对 NPM 包进行建模;重要的成员谓词包括
    • NPMPackage.getPackageName() 返回此包的名称。
    • NPMPackage.getPackageJSON() 返回此包的 package.json 文件。
    • NPMPackage.getNodeModulesFolder() 返回此包的 node_modules 文件夹。
    • NPMPackage.getAModule() 返回属于此包的 Node.js 模块(不包括 node_modules 文件夹中的模块)。

作为这些类的使用示例,以下是一个查询,用于识别未使用的依赖项,即在 package.json 文件中列出的但未被任何 require 调用导入的模块依赖项

import javascript

from NPMPackage pkg, PackageDependencies deps, string name
where deps = pkg.getPackageJSON().getDependencies() and
deps.getADependency(name, _) and
not exists (Require req | req.getTopLevel() = pkg.getAModule() | name = req.getImportedPath().getValue())
select deps, "Unused dependency '" + name + "'."

React

semmle.javascript.frameworks.React 库通过 ReactComponent 类提供对使用 React 代码的支持,该类对以函数式或基于类的样式定义的 React 组件进行建模(ECMAScript 2015 类和旧式 React.createClass 类均受支持)。

数据库

SQL::SqlString 代表被解释为 SQL 命令的表达式。目前,我们对通过以下 npm 包发出的 SQL 命令进行建模:mysqlpgpg-poolsqlite3mssqlsequelize

类似地,类 NoSQL::Query 代表被 mongodbmongoose 包解释为 NoSQL 查询的表达式。

最后,类 DatabaseAccess 包含所有执行使用上述任何包的数据库访问的数据流节点。

例如,以下是一个查询,用于查找使用字符串连接(而不是基于模板的解决方案,这通常更安全)的 SQL 查询

import javascript

from SQL::SqlString ss
where ss instanceof AddExpr
select ss, "Use templating instead of string concatenation."

杂项

外部文件

semmle.javascript.Externs 库通过以下类提供对使用 外部文件 的支持

  • ExternalDecl:对所有不同类型的外部文件声明进行建模的通用超类;它定义了两个成员谓词
    • ExternalDecl.getQualifiedName() 返回声明的实体的完全限定名称。
    • ExternalDecl.getName() 返回声明的实体的非限定名称。
  • ExternalTypedefExternalDecl 的子类,代表类型声明;与其他外部文件声明不同,此类声明不声明在运行时存在的函数或对象,而只是为类型引入一个别名。
  • ExternalVarDeclExternalDecl 的子类,代表变量或函数声明;它定义了两个成员谓词
    • ExternalVarDecl.getInit() 返回与该声明关联的初始化程序(如果有);这可以是 FunctionExpr
    • ExternalVarDecl.getDocumentation() 返回与该声明关联的 JSDoc 注释。

在外部文件中声明的变量和函数要么是全局变量(由类 ExternalGlobalDecl 代表),要么是成员(由类 ExternalMemberDecl 代表)。

成员进一步细分为静态成员(类 ExternalStaticMemberDecl)和实例成员(类 ExternalInstanceMemberDecl)。

有关这些类和代表外部文件的其他类的更多详细信息,请参阅 API 文档

HTML

semmle.javascript.HTML 库提供对使用 HTML 文档的支持。它们被表示为 HTML::Element 节点的树,每个节点可能具有零个或多个由类 HTML::Attribute 代表的属性。

与抽象语法树表示类似,HTML::Element 具有成员谓词 getChild(i)getParent(),分别用于从元素导航到其第 i 个子元素及其父元素。使用谓词 HTML::Element.getAttribute(i) 获取元素的第 i 个属性,并使用 HTML::Element.getAttributeByName(n) 获取具有名称 n 的属性。

对于 HTML::Attribute,谓词 getName()getValue() 分别提供对属性的名称和值的访问。

HTML::ElementHTML::Attribute 都具有谓词 getRoot(),该谓词获取它们所属文档的根 HTML::Element

JSDoc

semmle.javascript.JSDoc 库提供对使用 JSDoc 注释 的支持。文档注释被解析为一个抽象语法树表示,它紧密遵循 Doctrine JSDoc 解析器采用的格式。

JSDoc 注释作为一个整体由类 JSDoc 的实体表示,而单个标签由类 JSDocTag 表示。这两个类重要的成员谓词包括

  • JSDoc.getDescription() 返回 JSDoc 注释的描述性标题(如果有)。
  • JSDoc.getComment()JSDoc 实体映射到其底层的 Comment 实体。
  • JSDocTag.getATag() 返回此 JSDoc 注释中的标签。
  • JSDocTag.getTitle() 返回此标签的标题;例如,@param 标签的标题为 "param"
  • JSDocTag.getName() 返回此标签记录的参数或变量的名称。
  • JSDocTag.getType() 返回此标签记录的参数或变量的类型。
  • JSDocTag.getDescription() 返回与该标签关联的描述。

JSDoc 注释中的类型由类 JSDocTypeExpr 及其子类表示,这些子类再次将类型表达式表示为抽象语法树。类型表达式的示例包括 JSDocAnyTypeExpr,它代表 “any” 类型 *,或 JSDocNullTypeExpr,它代表 null 类型。

例如,以下是一个查询,用于查找未指定记录参数名称的 @param 标签

import javascript

from JSDocTag t
where t.getTitle() = "param" and
not exists(t.getName())
select t, "@param tag is missing name."

有关这些类和代表 JSDoc 注释和类型表达式的其他类的完整详细信息,请参阅 API 文档

JSX

semmle.javascript.JSX 库提供对使用 JSX 代码 的支持。

与 HTML 文档的表示类似,JSX 片段被建模为 JSXElement 的树,每个节点可能具有零个或多个 JSXAttribute

然而,与 HTML 不同,JSX 与 JavaScript 交织在一起,因此 JSXElementExpr 的子类。与 HTML::Element 一样,它具有谓词 getAttribute(i)getAttributeByName(n) 来查找 JSX 元素的属性。其主体元素可以通过谓词 getABodyElement() 访问;请注意,此谓词的结果是任意表达式,可能是进一步的 JSXElement,也可能是插入到外部元素主体中的其他表达式。

JSXAttributeHTML::Attribute 类似,具有谓词 getName()getValue() 来访问属性名称和值。

JSON

semmle.javascript.JSON 库提供了对构建 CodeQL 数据库时 JavaScript 提取器处理的 JSON 文件的支持。

JSON 文件被建模为 JSON 值的树。每个 JSON 值都由一个 JSONValue 类实体表示,它提供了以下成员谓词

  • JSONValue.getParent() 返回此值所在的 JSON 对象或数组。
  • JSONValue.getChild(i) 返回此 JSON 对象或数组的第 i 个子项。

请注意,JSONValueLocatable 的子类,因此 Locatable 的常用成员谓词可用于确定 JSON 值出现的文件及其在该文件中的位置。

JSONValue 类具有以下子类

正则表达式

semmle.javascript.Regexp 库提供了对正则表达式字面量的支持。正则表达式字面量的语法结构表示为正则表达式项的抽象语法树,由 RegExpTerm 类建模。与 ASTNode 类似,RegExpTerm 类提供了成员谓词 getParent()getChild(i) 来导航语法树的结构。

RegExpTerm 的各种子类对不同类型的正则表达式构造和运算符进行了建模;有关详细信息,请参见 API 文档

YAML

semmle.javascript.YAML 库提供了对构建 CodeQL 数据库时 JavaScript 提取器处理的 YAML 文件的支持。

YAML 文件被建模为 YAML 节点的树。每个 YAML 节点都由一个 YAMLNode 类实体表示,它提供了以下成员谓词(除其他外):

  • YAMLNode.getParentNode() 返回此节点在语法上嵌套其中的 YAML 集合。
  • YAMLNode.getChildNode(i) 返回此节点的第 i 个子节点,YAMLNode.getAChildNode() 返回此节点的任何子节点。
  • YAMLNode.getTag() 返回此 YAML 节点的标记。
  • YAMLNode.getAnchor() 返回与此 YAML 节点关联的锚点(如果有)。
  • YAMLNode.eval() 返回此 YAML 节点在解析别名和包含后计算得到的 YAMLValue

YAML 中可用的各种标量值由 YAMLIntegerYAMLFloatYAMLTimestampYAMLBoolYAMLNullYAMLString 类表示。它们的共同超类是 YAMLScalar,它有一个成员谓词 getValue() 用于获取标量作为字符串的值。

YAMLMappingYAMLSequence 分别表示映射和序列,并且是 YAMLCollection 的子类。

别名节点由 YAMLAliasNode 类表示,而 YAMLMergeKeyYAMLInclude 分别表示合并键和 !include 指令。

谓词 YAMLMapping.maps(key, value) 对映射表示的键值关系进行建模,并考虑合并键。

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