本文分享自华为云社区《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 中代码里函数、类/接口声明、注解,以及注释的节点和节点属性。
2. CodeNavi 中的节点和节点属性
程序是由空格分隔的字符串组成的序列。在程序分析中,这一个个的字符串被称为"token",是源代码中的最小语法单位,是构成编程语言语法的基本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
我们只需要给代码的不同节点给出一个定义,然后通过条件语句来描述对这些节点的要求,使之符合缺陷检查的模式,就可以完成检查规则的定义。
2.1. CodeNavi中的节点和节点属性
程序是由空格分隔的字符串组成的序列。在程序分析中,这一个个的字符串被称为"token",是源代码中的最小语法单位,是构成编程语言语法的基本元素。
Token可以分为多种类型,常见的有关键字(如if、while)、标识符(变量名、函数名)、字面量(如数字、字符串)、运算符(如+、-、*、/)、分隔符(如逗号,、分号;)等。
我们只需要给代码的不同节点给出一个定义,然后通过条件语句来描述对这些节点的要求,使之符合缺陷检查的模式,就可以完成检查规则的定义。
2.2. 节点
-
图例
-
节点和子节点都使用个图例
-
规则语言中使用节点的 “英文名”,这样便于规则的编写。
2.3. 节点集
- 图例
- 节点的集合。
2.4. 属性
- 图例
- 规则语言中使用属性的 “英文名”,这样便于规则的编写。
3. 函数
在程序设计中,函数是一段封装好的代码块,它可以接受输入(参数),执行特定的任务,并可能返回结果(返回值)。
以下是函数的一些主要作用:
-
模块化
函数将程序分解成更小的、更易于管理和理解的部分。 -
参数化
函数可以接受不同的参数,使得同一段代码能够处理不同的数据。 -
错误隔离
将特定功能封装在函数中,有助于隔离错误,当函数出错时,不会影响到程序的其他部分。 -
代码复用
通过将重复使用的代码封装成函数,可以避免代码冗余,提高代码的可维护性。 -
抽象
函数隐藏了实现细节,只暴露接口,使得调用者无需了解函数内部的实现即可使用它。 -
多态性
在支持面向对象编程的语言中,函数(方法)可以展示多态性,即同一个函数名可以有不同的实现,具体使用哪个实现取决于调用它的对象类型。 -
可维护性
当需要修改某个功能时,只需修改对应的函数,而不必在程序的多个地方进行更改。 -
可测试性
函数可以独立于程序的其他部分进行测试,这有助于确保代码的正确性。
3.1. 函数声明(functionDeclaration)
- 代码样例
public static String sayHello(String name) {
// 注释
Sting say = "Hello "+ name;
System.out.println(say);
return say;
}
- 节点和属性结构
3.1.1. 函数声明基本信息
- 节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
name | 方法名 | 字符串 | public static void debugFirst() {} | functionDeclaration fd where fd.name == “debugFirst”; |
parameters | 参数节点集 | parameterDeclaration节点集合 | public void loopBlocks_for(boolean flag) {} | functionDeclaration fd where fd.parameters contain pa where pa.name == “flag”; |
type | 返回值的类型 | objectType节点 | public static String sayHello() { return “hello”; } | functionDeclaration fd where fd.type.name == “java.lang.String”; |
3.1.2. 函数声明体相关的信息
- 节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
firstStatement | 方法的第一个完整的语句 | 任意节点 | public String functionCase() { Boolean result = validNull(); return “”; } | functionDeclaration fd where fd.firstStatement contain variableDeclaration; |
lastStatement | 方法的最后一个完整的语句 | 任意节点 | public String functionCase() { Boolean result = validNull(); return “”; } | functionDeclaration fd where fd.lastStatement contain returnStatement; |
assignStatements | 赋值语句集 | assignStatement节点集合 | public void functionCalls_first_argument() { String s = “hello”; StringBuilder sb = new StringBuilder(""); } | functionDeclaration fd where fd.assignStatements.size() == 2; |
returnStatements | 返回语句集 | returnStatement节点集合 | public int returnStatements_return_int(int m) { if (m > 10) { if (m < 100) { return randomInt(); } } return 0; } | functionDeclaration fd where fd.returnStatements contain rs where rs in ifBlock; |
comments | 方法的注释 | comments节点 | public static String sayHello() { // 注释 } | functionDeclaration fd where fd.comments contain lineComment; |
ifBlocks | if集 | ifBlock节点集合 | public void loopBlocks_for(boolean flag) { if (flag) { for (int i = 0; i < 10; i++) { System.out.println(i); } } } | functionDeclaration fd where fd.ifBlocks contain ib where ib contain forBlock; |
loopBlocks | 循环集 | loopBlock节点集合 | public void loopBlocks_while(boolean flag) { int m = 10; while (m > 0) { m–; } } | functionDeclaration fd where fd.loopBlocks contain ib where ib is whileBlock; |
fieldAccesses | 成员变量访问集 | fieldAccesses节点集合 | public void fieldAccesses_boolean() { System.out.println(this.testFlag); } | functionDeclaration fd where fd.fieldAccesses contain fa where fa.name == “testFlag”; |
variableAccesses | 变量访问集 | variableAccess节点集合 | public void loopBlocks_while(boolean flag) { int m = 10; while (m > 0) { m–; } } | functionDeclaration fd where fd.variableAccesses contain va where va.name == “m”; |
functionCalls | 函数调用集 | functionCall节点集合 | public void assignStatements_function_call_to_variable() { boolean result = “hello”.equals(“no”); System.out.println(result); } | functionDeclaration fd where fd.functionCalls contain fc where fc.name == “equals”; |
3.1.3. 函数继承、扩展的信息
- 节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
subMethods | 方法重写的子类方法集 | functionDecalration节点集合 | public class FatherDemo { public void fatherDemoMethod() { // 告警行 System.out.println(“father class”); } public void demoMethod() { } } public class SonDemo extends FatherDemo { public void fatherDemoMethod() { System.out.println(“Son class”); } } | functionDeclaration fd where and( fd.name == “fatherDemoMethod”, fd.subMethods contain fd2 where fd2.name == “fatherDemoMethod” ); |
superMethods | 方法重写的父类方法集 | functionDecalration节点集合 | public class FatherDemo { public void fatherDemoMethod() { // 告警行 System.out.println(“father class”); } public void demoMethod() { } } br>public class SonDemo extends FatherDemo { public void fatherDemoMethod() { System.out.println(“Son class”); } } | functionDeclaration fd where and( fd.name == “fatherDemoMethod”, fd.superMethods contain fd2 where fd2.name == “fatherDemoMethod” ); |
3.1.4. 函数调用信息
- 节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
functionUsages | 调用方法的位置集 | functionCall节点集合 | public static void debugFirst() {} public void debugSecond(int param) { FunctionTest.debugFirst(); } | functionDeclaration fd where and( fd.name == “debugFirst”, fd.functionUsages contain fc where fc.enclosingFunctionName == “debugSecond” ); |
callers | 调用方法的方法声明集 | functionDeclaration节点集合 | public static void debugFirst() {} public void debugSecond(int param) { FunctionTest.debugFirst(); } | functionDeclaration fd where and( fd.name == “debugFirst”, fd.callers contain cc where cc.name == “debugSecond” ); |
3.2. 函数入参(argument)
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
name | 参数名 | 字符串 | System.out.println(i) | argument arg where arg.name == “i”; |
argumentIndex | 参数的index | 数值 | System.out.println(i) | argument arg where arg.argumentIndex == 0; |
argumentHolder | 使用该参数的方法调用 | functionCall节点 | System.out.println(i) | argument arg where arg.argumentHolder.name == “println”; |
3.3. 方法调用(functionCall)
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
name | 方法名 | 字符串 | Objects.isNull(str); | functionCall fc where fc.name == “isNull”; |
function | 调用的方法 | functionDeclaration节点 | public String functionCase() { Boolean result = validNull(); return “”; } public Boolean validNull() { return false; } } | functionCall fc where fc.function fd where and( fd is functionDeclaration, fd.name == “validNull”, fd.returnStatements contain rs where rs.returnValue.value == false ); |
possibleFunctions | 调用的方法的所有方法。调用的方法及其重写与被重写的方法。 多态类创建对象时,声明类型为父类,实例化类型为子类型,调用对象方法时function属性应定位到子类的方法中。 | functionDeclaration节点 | public static void main(String[] args) { Son sg = new GrandSon(); sg.run(); //父类和子类都有这个方法 } | functionCall fc2 where and( fc2.base.name == “sg”, fc2.possibleFuncs contain fun where and( fun.enclosingClass.name endWith “GrandSon”, fun contain stringLiteral sl1 where sl1.value == “Grandson is running” ) ); |
base | 直接调用者 | valueAccess类节点、functionCall节点等 | MyRandom.aa(); | functionCall fc where and( fc.name == “aa”, fc.base.name == “MyRandom” ); |
rootBase | 根调用者 | valueAccess类节点、functionCall节点等 | a().b().c(); | functionCall fc where and( fc.name == “c”, fc.rootBase is functionCall, fc.rootBase.name == “a” ); |
arguments | 参数集 | valueAccess类节点、functionCall节点等的集合 | sb.append(s.toUpperCase(Locale.ENGLISH)); | functionCall fc where fc.arguments contain arg where and( arg is functionCall, arg.name == “toUpperCase”, arg.arguments contain fieldAccess ); |
arguments[n] | 第n个入参 | valueAccess类节点、functionCall节点等的集合 | add(a, b); | functionCall fc where fc.arguments[1].name == “b”; |
4. 类/接口声明(recordDeclaration)
类(Class)和接口(Interface)是面向对象编程(OOP)的两个基本构件,它们各自有着不同的作用和特点:
-
类(Class)
-
定义数据结构
类可以定义一组属性(变量)和方法(函数),这些属性和方法共同描述了一个对象的状态和行为。 -
创建对象
通过类可以实例化对象,每个对象都是类的一个实例,拥有自己的状态和行为。 -
封装
类可以将数据和操作数据的方法组合在一起,并隐藏内部实现细节,只提供必要的接口给外部访问。 -
继承
类可以继承其他类的属性和方法,实现代码复用和层次结构的建立。 -
多态
类可以通过重写(Override)父类的方法实现多态,使得同一个接口可以有不同的实现。 -
实现功能
类可以包含业务逻辑和功能实现,是程序功能的具体体现。
-
-
接口(Interface)
-
定义规范
接口定义了一组方法规范,但不实现这些方法,它规定了实现类必须遵循的契约。 -
实现多态
接口允许不同的类实现同一个接口,但可以有不同的实现方式,这是多态的另一种体现。 -
解耦
接口作为类之间的契约,可以降低类之间的耦合度,提高系统的灵活性和可扩展性。 -
扩展性
通过接口,可以在不修改现有代码的情况下引入新功能,只需实现相应的接口即可。 -
规范约束
接口可以作为规范约束,确保实现类遵循一定的设计原则和编程模式。
-
-
样例代码
// 类的定义
class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
// 接口的定义
interface Flyable {
void fly();
}
// 实现接口的类
class Bird implements Flyable {
private String name;
public Bird(String name) {
this.name = name;
}
@Override
public void fly() {
System.out.println(name + " is flying.");
}
}
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
name | 包名/类名 | 字符串 | package com.dsl.sourcefile.record; public class TypeTest { } | recordDeclaration rd where rd.name == “com.dsl.sourcefile.record.TypeTest”; |
comments | 注释集 | comment节点集合 | // 注释 public class TypeTest { } | recordDeclaration rd where rd.comments contain lineComment; |
kind | 类类型,可能是interface或class | 字符串 | public class TypeTest { } | recordDeclaration rd where rd.kind == “class”; |
superTypes | 父类和接口集 | recordDeclartion节点集合 | class MyList extends ArrayList<String> {} class SecondMyList extends MyList {} | recordDeclaration rd where rd.superTypes contain sType where sType.name endWith “ArrayList”; |
directSuperTypes | 父类和接口的直接集 | recordDeclartion节点集合 | class MyList extends ArrayList<String> {} class SecondMyList extends MyList {} | recordDeclaration rd where rd.directSuperTypes contain sType where sType.name endWith “ArrayList”; |
definedFields | 字段集 | fieldDeclaration节点集合 | class RecordDeclarationDemo extends Father { private static int COUNT = 0; } | recordDeclaration rd where rd.definedFields.size() == 1; |
definedConstructors | 构造方法集 | functionCall节点集合 | class RecordDeclarationDemo extends Father { private static int COUNT = 0; RecordDeclarationDemo() {} } | recordDeclaration rd where rd.definedConstructors.size() == 1; |
definedMethods | 方法集 | functionCall节点集合 | class RecordDeclarationDemo extends Father { private static int COUNT = 0; RecordDeclarationDemo() {} public void recordDeclarationMethod01() {} } | recordDeclaration rd where and( rd.definedMethods.size() == 1, rd.definedMethods contain fd where fd.name == “recordDeclarationMethod01” ); |
5. 注解(annotation)
注解(Annotation)是一种特殊的标记,它为我们提供了一种元数据(metadata)的方式,可以用于类、方法、变量、参数以及包等元素上。注解不会直接影响程序的运行,但它们可以被编译器或运行时环境用来生成额外的代码或者改变代码的行为。
注解提供了一种灵活的方式来扩展语言的能力,而不需要修改语言本身。
以下是注解的一些主要作用:
-
提供元数据
注解可以为代码提供额外的信息,这些信息可以被编译器、运行时环境或其他工具使用。 -
编译时检查
某些注解可以用来指示编译器执行特定的检查,例如@Override注解确保被注解的方法确实是覆盖了父类中的方法。 -
代码生成
注解可以用于代码生成,例如使用@Entity注解的类可以由JPA(Java Persistence API)提供者用来生成数据库表。 -
依赖注入
在依赖注入框架中,注解用于标记需要被框架自动注入的对象,如Google Guice的@Inject。 -
配置框架
许多现代框架使用注解来配置框架的行为,如Spring的@Controller、@Service、@Repository等。 -
安全性
注解可以用于标记安全相关的代码,如Java EE中的@RolesAllowed注解,用来定义哪些角色可以访问特定的方法。 -
运行时处理
有些注解可以在程序运行时被框架或库读取,并根据注解信息改变程序的行为,如Spring框架中的@Autowired。 -
测试支持
注解可以用于测试框架中,例如JUnit的@Test、@Before、@After等,用来标记测试方法和测试前的准备工作。 -
代码样例
public class Test extends ArrayList {
@TheAnnotation(isOk = false, num = 3, str = Test.test1)
public void functionWithAnnotation() {
}
}
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
name | 名称 | 字符串 | @TheAnnotation(isOk = false) | annotation ann where ann.name == “TheAnnotation”; |
annoMembers | 注解成员集 | annomember节点集合 | @TheAnnotation(isOk = false, num = 3, str = Test.test1) | annotation ann where ann.annoMembers contain am where am.name == “isOk”; |
5.1. 注解项(annoMember)
- 节点属性
名称 | 描述 | 值类型 | 示例 | DSL规则 |
---|---|---|---|---|
name | 名称 | 字符串 | @TheAnnotation(isOk = false, num = 3, str = Test.test1) | annotation ann where ann.annoMembers contain am where am.name == “str”; |
annoValue | 注解项的值 | literal、enumConstantAccess、fieldAcccess节点 | @TheAnnotation(isOk = false, num = 3, str = Test.test1) | annotation ann where ann.annoMembers contain am where am.annoValue amv where and( amv is fieldAccess, amv.name == “test1” ); |
6. 注释
在程序中,注释是一个非常重要的组成部分,它对代码的可读性、可维护性和团队协作有着重要的作用。
以下是注释的一些主要作用:
-
解释代码
注释可以解释复杂的代码段或算法,帮助其他开发者或未来的自己理解代码的意图。 -
标记TODO
开发者可以在代码中使用注释标记需要完成的任务(TODO),以便后续进行跟进。 -
法律声明
在软件的版权声明或许可证声明中,注释用来提供法律信息。 -
版本控制信息
在某些情况下,注释可以用来记录代码的版本信息或与版本控制系统中的提交相关联。 -
配置信息
在配置文件或脚本中,注释可以解释配置选项的作用。 -
提醒注意事项
在代码中可能存在一些特殊情况或陷阱,注释可以用来提醒开发者注意这些细节。 -
记录变更
在代码修改时,注释可以用来记录变更的原因和背景,方便追踪历史和理解变更的动机。 -
提供示例
在某些情况下,注释可以提供代码使用的示例,尤其是公共API或库的文档中。 -
文档生成
某些注释(如JavaDoc)可以被工具自动解析,生成API文档或其他形式的文档。 -
提高代码可读性
良好的注释可以使代码更加易于阅读和理解,尤其是对于复杂的逻辑或不常见的实现。
6.1. 注释(comment)
- 代码样例
// function description
/* function description */
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
content | 注释的内容 | 字符串 | // function declaration public void getInstance(String des) {} | comment cm where cm.content match “.*”; |
commentedNode | 被注释的节点 | 节点 | // function declaration public void getInstance(String des) {} | lineComment cm where cm.commentedNode is functionDeclaration; |
6.2. 行注释(lineComment)
- 代码样例
// function description
public void getInstance(String des) {
// todosomething
}
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
content | 注释的内容 | 字符串 | // function declaration public void getInstance(String des) {} | comment cm where cm.content match “.function.”; |
commentedNode | 被注释的节点 | 节点 | // function declaration public void getInstance(String des) {} | lineComment cm where cm.commentedNode is functionDeclaration; |
6.3. 块注释(blockComment)
- 代码样例
/* function description */
public void getInstance(String des) {
// todosomething
}
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
content | 注释的内容 | 字符串 | /* block comment */ | blockComment bc where bc.content contain “block comment”; |
commentedNode | 被注释的节点 | 节点 | /* block comment */ int a = 2; | blockComment bc where bc.commentedNode contain variableDeclaration; |
6.4. javadoc注释(javadocComment)
- 代码样例
/**
* 功能描述
*
* @since 2023-03-20
*/
public class EncryptCommon {
// todosomething
}
-
图例
-
节点属性
名称 | 描述 | 值类型 | 示例 | DSL 规则 |
---|---|---|---|---|
content | 注释的内容 | 字符串 | /** * 功能描述 * * @since 2023-03-20 */ public class EncryptCommon {} | javadocComment jd where jd.content contain “@since”; |
commentedNode | 被注释的节点 | 节点 | /** * 功能描述 * * @since 2023-03-20 */ public class EncryptCommon {} | javadocComment jd where jd.commentedNode is recordDeclaration; |
7. CodeNavi插件
-
在Vscode 的插件中,查询:
codenavi
,并安装。 -
使用插件的链接安装: https://marketplace.visualstudio.com/items?itemName=HuaweiCloud.codenavi