寻找适合编写HarmonyOS 鸿蒙Next静态分析规则的语言
寻找适合编写HarmonyOS 鸿蒙Next静态分析规则的语言 摘要:目前静态分析工具的主要痛点:无法开发自定义规则、对误报和漏报的规则无法快速修改,以及开发自定义规则有一定的难度。为了解决这些问题,我们需要寻找适合编写静态分析规则的语言。
本文分享自华为云社区《寻找适合编写静态分析规则的语言》,作者:Uncle_Tom。
1. 程序静态分析的作用
程序的静态分析是一种在不运行程序的情况下,通过分析程序代码来发现潜在的错误、安全漏洞、性能问题以及不符合编码规范的情况的技术。
程序的静态分析在现代软件安全中扮演着至关重要的角色。以下是静态分析在软件安全中的一些关键作用:
-
代码质量保证:
- 静态分析有助于确保代码符合安全编码标准和最佳实践,从而提高代码的质量和安全性。
- 参考:代码的安全检视
-
合规性检查:
- 许多行业标准和法规要求对软件进行安全合规性检查。静态分析工具可以帮助组织确保其软件产品符合这些标准和法规要求。
- 参考:一图看懂软件缺陷检查涉及的内容
-
漏洞检测:
- 静态分析工具可以在代码编写阶段检测潜在的安全漏洞,如SQL注入、跨站脚本攻击(XSS)、缓冲区溢出等。
- 参考:2023年最具威胁的25种安全漏洞(CWE TOP 25)
-
减少开发成本:
- 通过在开发早期阶段发现问题,静态分析可以减少后期修复的成本和时间,因为后期修复通常成本更高。
- 参考:构建DevSecOps中的代码三层防护体系
2. 静态分析工具的业务痛点
随着现在工程项目的代码量越来越大,同时开发框架的快速迭代和出现。静态分析工具所需要覆盖的场景也随之快速的增加,但静态分析工具所提供的是通用的检查能力,以及静态分析工具有限的迭代速度,无法满足客户不断出现的各种差异化需求。
2.1. 无法开发自定义规则
大多数静态分析工具由于设计之初多是为了解决特定的编码问题,所以没有考虑到后期的扩展和由用户完成规则的开发。如果需要提供自定义开发能力,需要从架构上重新设计,或者因为检查效率的问题,无法提供通用的检查配置和自定义能力。这将导致无法快速提供客户特定的需求的问题检查。用户只能通过需求反馈的方式,等待工具下个版本的发布,需要的闭环周期很长。
2.2. 对误报和漏报的规则无法快速修改
静态分析工具由于是对代码的静态分析,输入存在不确定性,这些不确定性导致工具在分析策略在上近似(Over-approximation)、下近似(Under-approximation)以及检查效率三者之间寻求某种平衡,这三个因素互相影响、互相制约。
- 上近似是指分析工具可能将一些实际上不会发生的程序行为错误地识别为可能发生的。换句话说,它可能导致分析结果过于宽泛,将一些安全的状态或行为错误地标记为不安全的。这也就导致了误报(false positives),即错误地将安全的代码标记为有问题。
- 下近似是指分析工具可能未能识别出实际上会发生的程序行为。这意味着分析结果可能过于保守,遗漏了一些潜在的问题。这就导致了漏报(false negatives),即未能发现实际存在的安全问题或错误。
- 效率是所有使用者一直追求的因素,快了还想快。但哪里有又想马儿跑得快,又想马儿不吃草的好事情。
由于这些原因,静态分析工具通常提供的是一种通用的检查规则,往往不能覆盖特定的场景,或覆盖场景不适合特定用户的使用条件,这也造成检查工具无法避免误报和漏报。比如说:从文件读对有的用户是危险,但对有的用户是安全的,工具无法识别用户读文件的实际场景,只能将所有从文件读设置为危险的。如果用户无法快速对工具规则进行修改,就会被检查结果中的误报或漏报造成困扰。
2.3. 开发自定义规则有一定的难度
分析引擎提供的自定义开发包,但也需要自定义规则的开发人员掌握静态分析的相关技术,用户上手的难度较大。且由于引擎对API的封装能力,对外提供的检查能力有限,在很大程度上限制了用户自定义规则的实现能力。
基于这些痛点,需要寻找一种适合编写静态分析规则的语言,来降低自定义规则的难度,使用户能够直接开发满足自己需求的规则,用户可以自己在很大程度上来控制和解决误报和漏报。
那么什么才是适合用户的编写静态分析规则的语言呢?
3. 寻找适合编写静态分析规则的语言
为了寻找适合用户的编写静态分析规则的语言,我们来看下我们常见的两种编程范式:声明式语言(Declarative Language)和命令式语言(Imperative Language)。这两者语言在如何描述程序行为和解决问题的方法上存在根本差异, 但同时各有优势和适用场景, 许多现代编程语言支持这两种范式, 允许程序员根据具体问题选择最合适的方法。
比较
声明式语言 | 命令式语言 | |
---|---|---|
问题表述方式 | 关注“做什么”(What to do),即描述期望的结果或目标状态,而不指定如何达到这个结果的具体步骤或过程。 | 关注“怎么做”(How to do it),即描述要执行的一系列步骤或命令,以改变系统的状态并最终达到期望的结果。 |
控制流 | 通常隐藏了控制流的细节,由语言的解释器或编译器来决定如何实现期望的状态。 | 程序员需要显式地编写控制流逻辑,如循环、条件判断和其他流程控制结构。 |
编程思维 | 鼓励一种更高层次的抽象思维,程序员可以专注于问题本身,而不需要关心实现细节。 | 要求程序员进行更详细的思考,包括数据结构的选择、算法的实现和程序状态的管理。 |
程序结构 | 程序结构通常围绕声明或规则展开,如规则、约束或模式。 | 程序结构通常围绕操作和状态变化展开,如变量的定义、修改和程序流程的控制。 |
错误处理和调试 | 由于隐藏了实现细节,可能会使调试和错误追踪更具挑战性。 | 由于程序的每一步都是显式的,可能更容易跟踪程序的执行过程和定位错误。 |
适用场景 | 适合于规则驱动、配置密集或数据查询等场景。 | 适合于需要精细控制程序执行流程和状态变化的场景。 |
例子 | SQL(数据库查询语言,只需指定要检索的数据,而不需要描述检索过程)、HTML(描述网页的结构和内容)、CSS(描述网页的样式)和函数式编程语言如 Haskell。 | C、Java、Python(尽管 Python 也支持函数式编程特性),它们使用变量、赋值、循环和条件语句来控制程序的执行。 |
具体来看一个声明式语言和命令式语言对问题的不同解决方式。
- 问题:从一个人群中挑出成年人;
- 具体条件:选出的人年龄大于等于 18 岁。
命令式语言 – Java 语言
public List<Person> selectAdults(List<Person> persons){
List<Person> result = new ArrayList<>();
for (Person person : persons) {
if (person.getAge() >= 18) {
result.add(person);
}
}
return result;
}
声明式语言 – SQL 语言
SELECT * FROM Persons WHERE Age >= 18;
从这个例子可以看出来,声明式语言更适合用户的使用,这也式为什么 SQL 语言在短时间内能够迅速的被推广和使用。
声明式语言的特点,也正是我们正在寻找的适合编写静态分析规则的语言。用户只需要关注:“做什么”(What to do),即描述期望的结果或目标状态,而不指定如何达到这个结果的具体步骤或过程。
我们也可以把这个检查语言称为一种领域特定语言(Domain Specific Language,DSL),为特定领域或问题域定制的语言,专注于解决特定类型的问题。这个语言只专注于 – 编写程序静态分析的规则。
这里没有直接使用自然语言,主要是自然语言存在表述上的差异和描述的准确性的问题。当然随着大模型的越来越成熟,直接通过自然语言完成规则的编写,也离我们越来越近了。但不管怎样,在识别到检查条件后,还是需要有一个引擎将这些约束条件转换成具体查询的程序语言,完成问题代码的搜索,这就像 SQL 语言负责描述条件,还需要一个 SQL 的查询引擎,完成 SQL 语言的解析和实施查询。
4. DSL 在程序静态分析中的应用举例
4.1. 编写检查规则
- 检查问题:生产环境中不应该有调试代码。
- 问题检查条件:
- 查找所有函数声明;
- 并且(And):函数名以"debug"开头;
- 并且(And):函数只有一个参数;
- 并且(And):参数类型为"java.util.List"。
/**
* 检查问题:生产环境中不应该有调试代码。
* 问题检查条件:
* - 查找所有函数声明;
* - 并且(And):函数名以"debug"开头;
* - 并且(And):函数只有一个参数;
* - 并且(And):参数类型为"java.util.List"。
*/
functionDeclaration fd where
and(
fd.name startWith "debug",
fd.parameters.size() == 1,
fd.parameters[0].type.name == "java.util.List"
);
4.2. 替换已有工具规则,并增加检查条件
4.2.1. 实现原有检查规则
- 原有检测问题:继承
java.util.TimerTask
类重写 run 方法,run 方法的实现要有 try-catch 保护。 - 检查的条件:
- 查找类继承自
java.util.TimerTask
; - 并且(and):重写了 run 方法;
- 并且(and):run 方法中没有 try-catch。
- 查找类继承自
/**
* 检查问题:继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
*/
functionDeclaration fd where
and(
fd.enclosingClass.superTypes contain parType where
parType.name == "java.util.TimerTask",
fd.name == "run",
fd notContain exceptionBlock
);
4.2.2. 增加检查条件
- 检查问题:
- 继承
java.util.TimerTask
类重写 run 方法,run 方法的实现要有 try-catch 保护。 - 在异常处理块中,需要有信息处理的函数: error 或 warn。
- 继承
- 问题检查条件:
- 查找类继承自
java.util.TimerTask
; - 并且(and):重写了 run 方法;
- 并且(and):run 方法中没有 try-catch。
- 或者(or): run 方法中有异常处理块;
- 并且(and): 异常处理块中有函数调用;
- 并且(and):函数名为:error 或 warn。
- 查找类继承自
/**
* 检查问题:
* - 继承 java.util.TimerTask 类重写 run 方法,run 方法的实现要有 try-catch 保护。
* - 在异常处理块中,需要有信息处理的函数: error 或 warn。
* 问题检查条件:
* - 查找类继承自 java.util.TimerTask;
* - 并且(and):重写了 run 方法;
* - 并且(and):run 方法中没有 try-catch。
* - 或者(or): run 方法中有异常处理块;
* - 并且(and): 异常处理块中有函数调用;
* - 并且(and):函数名为:error 或 warn。
*/
functionDeclaration fd where
and(
fd.enclosingClass.superTypes contain parType where
parType.name == "java.util.TimerTask",
fd.name == "run",
or(
fd notContain exceptionBlock,
fd contain exceptionBlock eb where
eb contain functionCall fc where
fc.name notMatch "error|warn"
)
);
5. 结论
通过上面两个案例,我们可以看到这个编写自定义规则的 DSL 语言,能够:
- 实现规则的编写;
- 实现规则的改进,降低误报率和漏报率;
- 降低了检查规则的开发难度。
欢迎大家试用这个插件,并给出反馈意见。
- 在 vscode 插件中查询:codenavi 添加插件即可。
更多关于寻找适合编写HarmonyOS 鸿蒙Next静态分析规则的语言的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html