【南大静态代码分析】作业 2:常量传播和 Worklist 求解器
作业 2:常量传播和 Worklist 求解器
题目链接:https://tai-e.pascal-lab.net/pa2.html
评测链接:https://oj.pascal-lab.net/problem
作业出品:南京大学《软件分析》课程,谭添、李樾
项目结构讲解
实验进行之前,要对项目中类和方法充分理解,找到每个类所对应的代码分析领域中的部分。
| 类 | 含义 |
|---|---|
CFG |
一个方法的控制流图。 |
IR |
一个非抽象方法的方法体的中间表示,其中包含方法签名的参数(parameters)、方法体中的变量(variables)和方法体中的所有中间表示语句(Stmts)。这里需要注意一点,一个方法体的中间表示由多个中间表示语句(Stmts)组成。 |
Stmt |
一条中间表示语句。 |
Exp |
表达式的中间表示。要注意,表达式不是语句。 |
CPFact |
建立变量 Var 到格上的值 Value 之间的映射,代表 Data Flow Fact (IN,OUT)。一个程序点(program point)对应一个 CPFact 。 |
DataflowResult |
包含控制流图中的所有 Data Flow Fact 。 |

实现常量传播
算法伪代码描述:

newBoundaryFact:负责创建和初始化虚拟结点的Data Flow Fact。但是注意要把方法参数初始化为NAC。

原因是为了 safe-approximation ,我们不知道通过形参传递过来的参数是否是常量,所以为了安全,假设所有参数都是 NAC ,当然这样会导致精度损失问题,后面通过过程间分析可以有效解决这个问题。
根据题目要求,不是所有类型的参数都考虑,只有能转换成 int 类型的参数才考虑,所以别忘了用 canHoldInt 过滤一下。
@Override
public CPFact newBoundaryFact(CFG<Stmt> cfg) {
// TODO - finish me
CPFact cpFact = new CPFact();
for (Var param : cfg.getIR().getParams()) {
if (canHoldInt(param)) { // 只考虑可转换int类型的参数
cpFact.update(param, Value.getNAC()); // 建立参数到格上值(NAC)的映射
}
}
return cpFact;
}
newInitialFact:负责创建和初始化控制流图中除了Entry和Exit之外的结点的Data Flow Fact。
控制流图中一个结点的 IN 和 OUT 分别对应一个 Data Flow Fact ,记录当前程序点时变量的状态。
直接创建一个空的 CPFact 即可,方法体内还没有开始扫描。
@Override
public CPFact newInitialFact() {
// TODO - finish me
return new CPFact();
}
meetInto:负责处理transfer function之前可能遇到多个OUT时的合并处理。
具体的合并操作通过调用 meetValue 来处理。
@Override
public void meetInto(CPFact fact, CPFact target) {
// TODO - finish me
for (Var var : fact.keySet()) {
Value v1 = fact.get(var);
Value v2 = target.get(var);
target.update(var, meetValue(v1, v2));
}
}
meetValue:负责对格上的值进行合并。

分三种情况实现即可。
/**
* Meets two Values.
*/
public Value meetValue(Value v1, Value v2) {
// TODO - finish me
if (v1.isNAC() || v2.isNAC()) {
return Value.getNAC();
} else if (v1.isUndef()) {
return v2;
} else if (v2.isUndef()) {
return v1;
} else if (v1.isConstant() && v2.isConstant()) {
if (v1.getConstant() == v2.getConstant()) {
return v1;
} else {
return Value.getNAC();
}
} else {
return Value.getNAC();
}
}
transferNode:负责实现控制流图中结点的transfer function。如果OUT改变,返回true;否则返回false。
stmt 表示结点中的一条中间表示,一个结点只有一个中间表示。
题目要求只需要对赋值语句处理,所以用 DefinitionStmt 类型过滤。
对于所有赋值语句,只考虑具有左值,并且左值是变量且类型可以转换成 int 的语句。这些语句的右值是一个表达式,可能是常量,也能是变量、二元表达式。这个右值表达式的值将通过 evaluate 函数计算。
对于其他类型的语句,不做处理,out 直接复制 in 即可,相当于经过一个恒等函数。
@Override
public boolean transferNode(Stmt stmt, CPFact in, CPFact out) {
// TODO - finish me
CPFact copy = in.copy(); // 复制in给copy,避免影响in。
if (stmt instanceof DefinitionStmt) { // 只处理赋值语句
if (stmt.getDef().isPresent()) { // 如果左值存在
LValue lValue = stmt.getDef().get(); // 获取左值
if ((lValue instanceof Var) && canHoldInt((Var) lValue)) { // 对于符合条件的左值
copy.update((Var) lValue, evaluate(((DefinitionStmt<?, ?>) stmt).getRValue(), copy)); // 计算右值表达式的值用来更新左值变量在格上的值
}
}
}
return out.copyFrom(copy); // copy复制给out。copy和in相比,有更新,返回true;反之返回false
}
evaluate:负责表达式值的计算。
表达式分三种情况讨论
- 常量:直接赋值。
- 变量:获取变量的值再赋值。
- 二元运算:针对共 12 中运算分别处理。

有几个易错点,容易卡评测:
NAC / 0 = Undef:这个点没有明确在题目中说明,但确实存在,需要单独处理。- 其他类型的表达式返回
NAF:不仅仅说上面三种类型之外的表达式,二元运算中有可能还有别的类型的运算,也需要返回NAF。
/**
* Evaluates the {@link Value} of given expression.
*
* @param exp the expression to be evaluated
* @param in IN fact of the statement
* @return the resulting {@link Value}
*/
public static Value evaluate(Exp exp, CPFact in) {
// TODO - finish me
if (exp instanceof Var) { // 变量
return in.get((Var) exp);
} else if (exp instanceof IntLiteral) { // 常量
return Value.makeConstant(((IntLiteral) exp).getValue());
} else if (exp instanceof BinaryExp) { // 二元运算
Value v1 = in.get(((BinaryExp) exp).getOperand1()); // 获取运算分量在格上的值
Value v2 = in.get(((BinaryExp) exp).getOperand2());
if (v1.isNAC() || v2.isNAC()) { // 易错点1:NAC / 0 = Undef
if (v1.isNAC() && v2.isConstant() && exp instanceof ArithmeticExp) {
ArithmeticExp.Op operator = ((ArithmeticExp) exp).getOperator();
if (operator == ArithmeticExp.Op.DIV || operator == ArithmeticExp.Op.REM) {
if (v2.getConstant() == 0) return Value.getUndef();
}
}
return Value.getNAC();
}
if (v1.isUndef() || v2.isUndef()) {
return Value.getUndef();
}
if (exp instanceof ArithmeticExp) {
ArithmeticExp.Op operator = ((ArithmeticExp) exp).getOperator();
switch (operator) {
case ADD -> {
return Value.makeConstant(v1.getConstant() + v2.getConstant());
}
case DIV -> {
if (v2.getConstant() == 0) return Value.getUndef();
return Value.makeConstant(v1.getConstant() / v2.getConstant());
}
case MUL -> {
return Value.makeConstant(v1.getConstant() * v2.getConstant());
}
case SUB -> {
return Value.makeConstant(v1.getConstant() - v2.getConstant());
}
case REM -> {
if (v2.getConstant() == 0) return Value.getUndef();
return Value.makeConstant(v1.getConstant() % v2.getConstant());
}
}
} else if (exp instanceof ConditionExp) {
ConditionExp.Op operator = ((ConditionExp) exp).getOperator();
switch (operator) {
case EQ -> {
if (v1.getConstant() == v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
case GE -> {
if (v1.getConstant() >= v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
case GT -> {
if (v1.getConstant() > v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
case LE -> {
if (v1.getConstant() <= v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
case LT -> {
if (v1.getConstant() < v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
case NE -> {
if (v1.getConstant() != v2.getConstant()) return Value.makeConstant(1);
else return Value.makeConstant(0);
}
}
} else if (exp instanceof BitwiseExp) {
BitwiseExp.Op operator = ((BitwiseExp) exp).getOperator();
switch (operator) {
case OR -> {
return Value.makeConstant(v1.getConstant() | v2.getConstant());
}
case AND -> {
return Value.makeConstant(v1.getConstant() & v2.getConstant());
}
case XOR -> {
return Value.makeConstant(v1.getConstant() ^ v2.getConstant());
}
}
} else if (exp instanceof ShiftExp) {
ShiftExp.Op operator = ((ShiftExp) exp).getOperator();
switch (operator) {
case SHL -> {
return Value.makeConstant(v1.getConstant() << v2.getConstant());
}
case SHR -> {
return Value.makeConstant(v1.getConstant() >> v2.getConstant());
}
case USHR -> {
return Value.makeConstant(v1.getConstant() >>> v2.getConstant());
}
}
}
else { // 易错点2:二元表达式中的其他类型表达式
return Value.getNAC();
}
}
return Value.getNAC();
}
实现 Worklist 求解器
算法伪代码描述:

initializeForward:初始化所有的Data Flow Fact。注意虚拟节点Entry的OUT别忘了
protected void initializeForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {
// TODO - finish me
result.setOutFact(cfg.getEntry(), analysis.newBoundaryFact(cfg));
for (Node node : cfg) {
if (cfg.isEntry(node)) continue;
result.setInFact(node, analysis.newInitialFact());
result.setOutFact(node, analysis.newInitialFact());
}
}
doSolveForward:负责实现 Worklist 求解器具体步骤。
@Override
protected void doSolveForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {
// TODO - finish me
ArrayDeque<Node> worklist = new ArrayDeque<>(); // 双端堆栈当队列用
for (Node node : cfg) { // 添加所有结点到队列中
if (cfg.isEntry(node)) {
continue;
}
worklist.addLast(node);
}
while (!worklist.isEmpty()) {
Node node = worklist.pollFirst(); // 弹出队头结点
for (Node pred : cfg.getPredsOf(node)) { // 对该结点以及所有前驱结点的OUT做meet
analysis.meetInto(result.getOutFact(pred), result.getInFact(node));
}
boolean f = analysis.transferNode(node, result.getInFact(node), result.getOutFact(node));
if (f) { // 如果该节点OUT发生了变化,将其所有后继节点添加到队列
for (Node succ : cfg.getSuccsOf(node)) {
worklist.addLast(succ);
}
}
}
评测结果

【南大静态代码分析】作业 2:常量传播和 Worklist 求解器的更多相关文章
- 南大《软件分析》课程笔记——Intermediate Representation
南大<软件分析>--Intermediate Representation @(静态分析) Content 编译器和静态分析的关系 AST vs IR IR:3-地址代码(3AC) 实际静 ...
- 共创力咨询推出《静态代码分析(PCLint)高级实务培训》课程!
[课程背景] C/C++语言的语法非常灵活性,尤其是指针及内存使用,这种灵活性使代码效率比较高,但同时也使得代码编写具有较大的随意性,另外C/C++编译器不进行强制类型检查,也不对数据边界和有效性进行 ...
- Https与Http,SSL,DevOps, 静态代码分析工具,RFID, SSH, 非对称加密算法(使用最广泛的一种是RSA), 数字签名, 数字证书
在URL前加https://前缀表明是用SSL加密的. 你的电脑与服务器之间收发的信息传输将更加安全. Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定. http和h ...
- Eclipse插件(导出UML图,打开文件资源管理器插件,静态代码分析工具PMD,在eclipse上安装插件)
目录 能够导出UML图的Eclipse插件 打开文件资源管理器插件 Java静态代码分析工具PMD 如何在eclipse上安装插件 JProfiler性能分析工具 从更新站点安装EclEmma 能够导 ...
- 南大《软件分析》课程笔记——Data Flow Analysis
南大<软件分析>--Data Flow Analysis @(静态分析) 目录 数据流分析概述 数据流分析应用 Reaching Definitions Analysis(may anal ...
- pmd静态代码分析
在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- C++静态代码分析PreFast
1历史 Prefast是微软研究院提出的静态代码分析工具.主要目的是通过分析代码的数据和控制信息来检测程序中的缺陷.需要强调的是,Prefast检测的缺项不仅仅是安全缺陷,但是安全缺陷类型是其检测的最 ...
- C++静态代码分析工具推荐——PVS-Studio
长假归来,最近一直没更新,节前本来就想写这篇了,一直到今天才有时间. 关于静态代码分析在维基百科上可以查到很详细的介绍:https://en.wikipedia.org/wiki/List_of_to ...
- 来试试这个来自静态代码分析工具PVS Studio提供C++的小测验吧
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:来试试这个来自静态代码分析工具PVS Studio提供C++的小测验吧.
随机推荐
- Lucas定理 、斯特灵公式
斯特灵公式是一条用来取n阶乘的近似值的数学公式. 公式为: 用该公式我们可以用来估算n阶乘的值:估算n阶乘的在任意进制下的位数. 如何计算在R进制下的位数:我们可以结合对数来计算,比如十进制就是lg( ...
- springboot整合mybatis步骤思路
/** * springboot整合mybatis步骤思路 * 依赖导入 * 建表 * 实体类 * mapper配置文件 * mapper接口 * yaml配置 * properties配置数据库连接 ...
- 性能测试常见面试题(Loadrunner)
https://blog.csdn.net/xiangxiupp/article/details/53862056
- netty自定义channel id
netty自定义channel id.netty custom channel id 搞搞netty时发现默认的id很长,无法直接自定义. 于是我网上搜索了search一下,发现没有相关文章,那就自己 ...
- javaGUI入门之swing(一)
javaGUI入门之swing(一) 前面学过javafx,发现他们有"异曲同工"之处,demo写起来也类似,无非是类名不一样.个人觉得实现一个桌面应用不应该只看用一种语言一种框架 ...
- Netty 组件介绍
BootStrap Netty 中的 BootStrap 分为两种:一种是客户端的 BootStrap:一种是服务端的 ServerBootStrap. 客户端的 BootStrap 初始化客户端,该 ...
- 22、Scaffold属性 抽屉菜单Drawer
在Scaffold组件里面传入drawer参数可以定义左侧边栏,传入endDrawer可以定义右侧边栏.侧边栏默 认是隐藏的,我们可以通过手指滑动显示侧边栏,也可以通过点击按钮显示侧边栏. class ...
- EFCore CodeFirst DBFirst Demo
1.简单Demo单独记录一下使用过程,Nuget对应.Net环境版本数据库包.EF Core 的 PMC 工具包. 2.CodeFirst 包管理器控制台(PMC)输入命令,迁移创建数据库: Add- ...
- 云图说|分钟级构建业务大屏——Astro大屏应用
本文分享自华为云社区<[云图说]第271期 Astro Canvas一站式数据可视化开发,分钟级构建业务大屏>,作者:阅识风云 . Astro大屏应用(Astro Canvas)是Astr ...
- 万物皆可Cassandra:HUAWEI Tag背后的神仙数据库
摘要:在GaussDB(for Cassandra)的支撑下,HUAWEI Tag防丢精灵表现出了非常强大的核心竞争力. 本文分享自华为云社区<万物皆可Cassandra--HUAWEI Tag ...