梦应逍遥 发表于 2024-7-8 21:27:58

编写代码检查规则的神器,解读CodeNavi语法结构

本文分享自华为云社区《CodeNavi 规则的语法结构》,作者: Uncle_Tom。
1. 代码和检查规则

1.1. 代码的构成

步伐是由空格分隔的字符串组成的序列。在步伐分析中,这一个个的字符串被称为"token",是源代码中的最小语法单位,是构成编程语言语法的基本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
步伐在编译过程中,词法分析器(Lexer)读取源代码并将其分解成一系列的token。语法分析器(Parser)会使用这些 token 来构建一个抽象语法树(Abstract Syntax Tree, AST),这个树结构表现了代码的语法结构。这个时候每个 token 也可以称为抽象语法树的节点,树上某个节点的分支就是这个节点的子节点。每个节点都会有节点类型、属性、值。
这些是步伐员非常容易明白的。
1.2. 检查规则

检查规则是查找代码节点中符合缺陷模式的检查规则。
通常静态分析工具的规则编写采用步伐语言,如许开辟人员除了要了解检查的问题本省,还必要明白静态分析理论,以及静态分析工具的框架,无形中增加了开辟的难度。所以我们一直期望寻找到一种更适合编写静态分析规则的语言,让开辟人员可以或许更好的专注于明白检查问题的本身,而不必要花更多的时间在如何实现上。
《寻找适合编写静态分析规则的语言》中我们给出了两个问题的解决,来描述我们对编写静态分析规则的期望。
例如:
检查问题:

[*]生产情况中不应该有调试代码。
问题检查条件:

[*]查找全部函数声明
[*]并且(And):函数名以"debug"开头
[*]并且(And):函数只有一个参数
[*]并且(And):参数类型为"java.util.List"
问题代码样例
package com.dsl;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.List;

/**
* 检查问题:生产环境中不应该有调试代码。
* 问题检查条件:
* - 查找所有函数声明;
* - 并且(And):函数名以"debug"开头;
* - 并且(And):函数只有一个参数;
* - 并且(And):参数类型为"java.util.List"。
*/
public class CheckDebug {
    private static final Logger LOG = LogManager.getLogger(CheckDebug.class);

    // 应检查出的问题函数
    public void debugFunction(List<String> msgs) {
      for (String msg : msgs) {
            LOG.error("print debug info: {}", msg);
      }
    }
}编写检查规则
https://bbs-img.huaweicloud.com/blogs/img/20240706/1720225886433285193.gif
DSL 写的检查规则
/**
* 检查问题:生产环境中不应该有调试代码。
* 问题检查条件:
* - 查找所有函数声明;
* - 并且(And):函数名以"debug"开头;
* - 并且(And):函数只有一个参数;
* - 并且(And):参数类型为"java.util.List"。
*/

functionDeclaration fd where
    and(
      fd.name startWith "debug",
      fd.parameters.size() == 1,
      fd.parameters.type.name == "java.util.List"
    );2. 规则语法结构

下面我们进一步给出这种用于编写代码检查规则的工具 CodeNavi 的语法结构。
2.1. 规则的语法

结构图
https://bbs-img.huaweicloud.com/blogs/img/20240705/1720191901602621355.png语法说明
规则语句是以分号 (;) 末端, 作为规则的终止符。
2.2. 语句结构

结构图
https://bbs-img.huaweicloud.com/blogs/img/20240705/1720191916645184442.png
 
语法说明
节点(node): 为待检测代码节点(token);
as 别名:给要查找的节点一个别名,便于背面的使用。as 可以省略。别名的命名规则如下:

[*]只能包含大小写英笔墨母和数字;
[*]数字只能出现在末端;
[*]不能和规则内置的节点或属性同名。
[*]例如:va1、fd、Field1都是合法的别名,1fc、v2a是非法别名。
查询条件(condition):筛选节点的条件;
2.3. 节点和节点属性

结构图
https://bbs-img.huaweicloud.com/blogs/img/20240705/1720191936945409349.png
 
语法说明

[*]节点(node):是代码对应语法树的节点或子节点;
[*]属性(attribute):是代码对应语法树的节点或子节点的属性;
[*]. : 节点、子节点、属性之间的连接符;
2.4. 条件表达式结构

结构图
https://bbs-img.huaweicloud.com/blogs/img/20240705/1720191954680768889.png
语法说明

[*]where: 条件表达式的标识;
[*]and:括号内的查询条件必须都为真;
[*]or:括号内的查询条件至少有一个为真;
[*]not:括号内的查询条件为假;
逻辑的连接祠是一种作用于查询条件的关键词。规则支持and、or 和 not 三个关联词。
逻辑的连接词必要和括号一同使用,括号中为具体的查询条件。其中,and 和 or 可以作用于多个查询条件(即括号中可以包含多个查询条件,以逗号分隔开),not 只能作用于一个查询条件。
逻辑连接词是一类作用于查询条件的特殊运算符(普通的运算符作用于节点或属性)
逻辑的连接词作用的查询条件也可以包含逻辑连接词。
示例
/*
* 问题检查条件:
* - 查找函数名为 debug 或 foo;
* - 并且(And): 参数数量为 1 或 3。
*/
functionDeclaration fd where
    and(or(fd.name == "debug", fd.name == "foo"),
      or(fd.parameters.size() == 1, fd.parameters.size() == 3));2.5. 条件判断(condition)

结构图
https://bbs-img.huaweicloud.com/blogs/img/20240705/1720191970775316691.png
2.5.1. 运算符

查询条件通过运算符作用于具体的节点或属性从而完成节点筛选。
根据运算符作用的属性种别,我们可以将运算符分为:

[*]通用运算符
[*]算术运算符
[*]字符串运算符
[*]布尔运算符
[*]节点运算符
运算符的左值为:节点或节点的属性。
2.5.1.1. 通用运算符(==, contain)

等于(==)、不等于(!=)

[*]数字

[*]运算符右值为:整数或小数。
[*]例如:arguments.size() == 3

[*]字符串

[*]运算符右值为:字符串常量。
[*]例如:name == “foo”

[*]布尔

[*]运算符右值为:布尔常量: true 或 false。
[*]例如:isPublic == true

[*]节点

[*]运算符右值为:空常量null。
[*]例如:initializer == null

包含(contain)、不包含(notContain)

[*]字符串

[*]运算符右值为:字符串,且包含时,返回真。
[*]例如:recordDeclaration where name contain "debug";,返回函数名中包含"debug" 时,返回真。

[*]节点

[*]运算符右值为:子查询条件,当子查询返回的节点满足时,返回真。
[*]例如:functionDeclaration where parameters contain param where param.name == "i1";,返回包含名为i1参数的方法声明。

2.5.1.2. 算术运算符(>, )、小于(=)、小于等于(
页: [1]
查看完整版本: 编写代码检查规则的神器,解读CodeNavi语法结构