本文分享自华为云社区《CodeNavi 中代码节点的基础属性》。作者: Uncle_Tom
1. 前期回顾
1.1. 《寻找适合编写静态分析规则的语言》
根据代码检查中的一些痛点,提出了希望寻找一种适合编写静态分析规则的语言。
- 可以满足用户对代码检查不断增加的各种需求;
- 使用户能够通过增加或减少对检查约束条件的控制,实现快速调整检查中出现的误报和漏报;
- 这种检查语言能够有较低的使用门槛,使用户更专注于检查业务,而不需要关注工具是如何实现的。
我们称这种检查规则语言为:CodeNavi。文中给出了这种检查规则语言的两个应用场景,以此来说明这种检查规则语言如何满足用户在编写静态检查规则时的期望。
1.2. 《CodeNavi 规则的语法结构》
介绍 CodeNavi 检查规则语言在编写规则的基本语法格式。CodeNavi 检查规则语言,通过对代码节点和节点属性的逻辑条件的组合来完成检查的约束条件,从而完成代码检查中需要满足的缺陷模式适配,找到满足要求的代码点。
1.3. 《CodeNavi 规则的基础节点和节点属性》
介绍 CodeNavi 检查规则语言在编写时需要使用基础节点和节点属性,这些基础节点包括:节点类型、字面量、一般变量、枚举、成员变量(字段),以及数组。
1.4. 《CodeNavi 中代码表达式的节点和节点属性》
介绍 CodeNavi 检查规则语言如何描述代码中的表达式。这些节点主要包括:对象创建表达式、强制类型转换、类型判断表达式、一元表达式、二元表达式、条件表达式/三目运算、方法引用表达式、lambda表达式,以及匿名内部类表达式。
1.5. 《CodeNavi 中代码语句的节点和节点属性》
介绍 CodeNavi 中代码里语句的节点和节点属性。
- 语句节点包括:赋值语句,
- 控制流语句包括:跳转语句、条件控制、Switch控制、循环控制、异常处理、静态语句、同步代码块。
1.6. 《CodeNavi 中代码函数的节点和节点属性》
介绍 CodeNavi 中代码里函数、类/接口声明、注解,以及注释的节点和节点属性。
1.7. 本篇概要
介绍 CodeNavi 中节点的通用属性。每个节点会有一些基础属性,用于得到这个节点所在类、函数、if、for、foreach、while或 try、catch 中的位置。
2. CodeNavi 中的节点和节点属性
程序是由空格分隔的字符串组成的序列。在程序分析中,这一个个的字符串被称为"token",是源代码中的最小语法单位,是构成编程语言语法的基本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
我们只需要给代码的不同节点给出一个定义,然后通过条件语句来描述对这些节点的要求,使之符合缺陷检查的模式,就可以完成检查规则的定义。
2.1. CodeNavi中的节点和节点属性
程序是由空格分隔的字符串组成的序列。在程序分析中,这一个个的字符串被称为"token",是源代码中的最小语法单位,是构成编程语言语法的基本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
我们只需要给代码的不同节点给出一个定义,然后通过条件语句来描述对这些节点的要求,使之符合缺陷检查的模式,就可以完成检查规则的定义。
2.2. 节点
-
图例

-
节点和子节点都使用个图例
-
规则语言中使用节点的 “英文名”,这样便于规则的编写。
2.3. 节点集
- 图例

- 节点的集合。
2.4. 属性
- 图例

- 规则语言中使用属性的 “英文名”,这样便于规则的编写。
3. 节点的通用属性
每个节点会有一些基础属性,用于得到这个节点所在类、函数、if、for、foreach、while或 try、catch 中的位置。
-
图例

-
节点和属性结构
名称
描述
值类型
示例
DSL 规则
annotations
节点的注解
annotation节点集合
@Parameter(names = {“ok”, “hello”, “yes”})
public void initArrayTest() {}
functionDeclaration fd where
fd.annotations contain anno where
anno.name == “Parameter”;
startLine
节点的起始行
数值
_
functionDeclaration fd where
fd.startLine == 21;
endLine
节点的结束行
数值
_
functionDeclaration fd where
fd.endLine == 23;
enclosingClass
节点所在的类
recordDeclaration节点
public class FunctionCallTest {
public void setHeader(String header, String option) {
new MyRandom().intNum();
}
public String getString(String ok, String ss) {
return ss;
}
public void getIt() {
setHeader(“Access-Control-Allow-Origin”, “*”);
}
}
functionCall fc where and(
fc.function.name == “setHeader”,
fc.function.enclosingClass.name endWith “FunctionCallTest”
);
enclosingClassName
节点所在的类名
字符串
new MyRandom().intNum();
functionCall fc where and(
fc.name == “intNum”,
fc.function.enclosingClassName endWith “MyRandom”
);
enclosingFunction
节点所在的方法
functionDeclaration节点
public String getString(String ok, String ss) {
return ss.replace(‘1’, ‘2’);
}
functionCall fc where and(
fc.name == “replace”,
fc.enclosingFunction.parameters.size() == 2
);
enclosingFunctionName
节点所在的方法名
字符串
public String getString(String ok, String ss) {
return ss.replace(‘1’, ‘2’);
}
functionCall fc where and(
fc.name == “replace”,
fc.enclosingFunctionName == “getString”
);
enclosingIf
节点所在的If块
ifBlock节点
private void testEnclosingIf() {
boolean flag = true;
if(!flag) {
int n = 0;
System.out.println(n);
while (n < 5) {
if (flag) {
System.out.println(“ifBlock2”); // 告警行
n++;
}
}
}
}
functionCall fc5 where and(
fc5.enclosingFunction.name == “testEnclosingIf”,
fc5.name == “println”,
fc5.enclosingIf.startLine == 117
);
enclosingFor
节点所在的for循环
forBlock节点
private void testEnclosingFor() {
int rows = 5;
int columns = 10;
for (int i = 0; i < rows; i++) {
int count = 0;
count++;
for (int j = 0; j < columns; j++) {
if (j % 2 == 0){
count++; // 告警行
}
}
}
}
unaryOperation uo1 where and(
uo1.enclosingWhile.size() == 0,
uo1.enclosingTry.size() == 0,
uo1.enclosingCatch.size() == 0,
uo1.enclosingForEach.size() == 0,
uo1.enclosingFunction.name == “testEnclosingFor”,
uo1.operand.name == “count”,
uo1.enclosingFor.startLine == 30
);
enclosingForEach
节点所在的forEach循环
forEachBlock节点
private void testEnclosingForEach() {
List<List> numbers = new ArrayList<>();
numbers.add(Arrays.asList(1, 2, 3));
numbers.add(Arrays.asList(4, 5, 6));
numbers.add(Arrays.asList(7, 8, 9));
for (List row : numbers) {
for (int num : row) {
System.out.println(num + " "); // 告警行
}
System.out.println();
}
}
functionCall fc1 where and(
fc1.enclosingFunction.name == “testEnclosingForEach”,
fc1.name == “println”,
fc1.enclosingForEach.startLine == 47
);
enclosingWhile
节点所在的while循环
whileBlock节点
private void testEnclosingWhile() {
int rows = 4;
int columns = 6;
int i = 0;
while (i < rows) {
int j = 0;
while (j < columns) {
System.out.println("* "); // 告警行
j++;
}
System.out.println();
i++;
}
}
functionCall fc2 where and(
fc2.enclosingFunction.name == “testEnclosingWhile”,
fc2.name == “println”,
fc2.enclosingWhile.startLine == 62
);
enclosingTry
节点所在的try块
tryBlock节点
private void testEnclosingTry() {
try (FileInputStream fileInputStream = new FileInputStream(“file.txt”)) {
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.print("Error reading file: " + e.getMessage()); // 告警行
}
} catch (IOException e) {
System.out.println("File not found: " + e.getMessage());
}
}
functionCall fc3 where and(
fc3.enclosingFunction.name == “testEnclosingTry”,
fc3.name == “print”,
fc3.enclosingTry contain
variableDeclaration
);
enclosingCatch
节点所在的catch块
catchBlock节点
private void testEnclosingCatch() {
try {
int[] arr = new int[5];
arr[10] = 20;
String str = null;
System.out.println(str.length());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(“数组越界异常:” + e.getMessage());
} catch (NullPointerException e) {
System.out.println(“空指针异常:” + e.getMessage());
} catch (Exception e) {
if (e instanceof IOException) {
System.out.println(“IO异常:” + e.getMessage()); // 告警行
} else {
System.out.println(“其他异常:” + e.getMessage()); // 告警行
}
} finally {
System.out.println(“程序执行完毕!”);
}
}
functionCall fc4 where and(
fc4.enclosingFunction.name == “testEnclosingCatch”,
fc4.name == “println”,
fc4.enclosingCatch.startLine == 98
);
4. 小结
本期是 CodeNavi 语法、节点、属性介绍的最后一期。 通过前面的几期介绍,已经完整的介绍了 CodeNavi 语法中和代码中节点的对应。根据代码的特征,我们可以通过 CodeNavi 语法完成对检测点的筛选,从而找到符合我们要求的问题代码。
之后我们将开始通过使用 CodeNavi 完成一些具体的检测规则,来说明 CodeNavi 在检测中的易用性。
CodeNavi 的很多部分还在完善和改进中,也希望大家能够参与其中,共建共享,给出更多的建议,建立一个良好的检查生态。
5. CodeNavi插件
-
在Vscode 的插件中,查询:
codenavi,并安装。

-
使用插件的链接安装: marketplace.visualstudio.com/items?itemN…
-
CodeNavi 介绍: