作业 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 FactINOUT)。一个程序点(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 :负责创建和初始化控制流图中除了 EntryExit 之外的结点的 Data Flow Fact

控制流图中一个结点的 INOUT 分别对应一个 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 :负责表达式值的计算。

表达式分三种情况讨论

  1. 常量:直接赋值。
  2. 变量:获取变量的值再赋值。
  3. 二元运算:针对共 12 中运算分别处理。

有几个易错点,容易卡评测:

  1. NAC / 0 = Undef:这个点没有明确在题目中说明,但确实存在,需要单独处理。
  2. 其他类型的表达式返回 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 。注意虚拟节点 EntryOUT 别忘了
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 求解器的更多相关文章

  1. 南大《软件分析》课程笔记——Intermediate Representation

    南大<软件分析>--Intermediate Representation @(静态分析) Content 编译器和静态分析的关系 AST vs IR IR:3-地址代码(3AC) 实际静 ...

  2. 共创力咨询推出《静态代码分析(PCLint)高级实务培训》课程!

    [课程背景] C/C++语言的语法非常灵活性,尤其是指针及内存使用,这种灵活性使代码效率比较高,但同时也使得代码编写具有较大的随意性,另外C/C++编译器不进行强制类型检查,也不对数据边界和有效性进行 ...

  3. Https与Http,SSL,DevOps, 静态代码分析工具,RFID, SSH, 非对称加密算法(使用最广泛的一种是RSA), 数字签名, 数字证书

    在URL前加https://前缀表明是用SSL加密的. 你的电脑与服务器之间收发的信息传输将更加安全. Web服务器启用SSL需要获得一个服务器证书并将该证书与要使用SSL的服务器绑定. http和h ...

  4. Eclipse插件(导出UML图,打开文件资源管理器插件,静态代码分析工具PMD,在eclipse上安装插件)

    目录 能够导出UML图的Eclipse插件 打开文件资源管理器插件 Java静态代码分析工具PMD 如何在eclipse上安装插件 JProfiler性能分析工具 从更新站点安装EclEmma 能够导 ...

  5. 南大《软件分析》课程笔记——Data Flow Analysis

    南大<软件分析>--Data Flow Analysis @(静态分析) 目录 数据流分析概述 数据流分析应用 Reaching Definitions Analysis(may anal ...

  6. pmd静态代码分析

    在正式进入测试之前,进行一定的静态代码分析及code review对代码质量及系统提高是有帮助的,以上为数据证明 Pmd 它是一个基于静态规则集的Java源码分析器,它可以识别出潜在的如下问题:– 可 ...

  7. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  8. C++静态代码分析PreFast

    1历史 Prefast是微软研究院提出的静态代码分析工具.主要目的是通过分析代码的数据和控制信息来检测程序中的缺陷.需要强调的是,Prefast检测的缺项不仅仅是安全缺陷,但是安全缺陷类型是其检测的最 ...

  9. C++静态代码分析工具推荐——PVS-Studio

    长假归来,最近一直没更新,节前本来就想写这篇了,一直到今天才有时间. 关于静态代码分析在维基百科上可以查到很详细的介绍:https://en.wikipedia.org/wiki/List_of_to ...

  10. 来试试这个来自静态代码分析工具PVS Studio提供C++的小测验吧

    博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:来试试这个来自静态代码分析工具PVS Studio提供C++的小测验吧.

随机推荐

  1. CatFly【汇编代码还原】

    CatFly[难度:1] 题目界面 下载附件,发现是dll文件,放到linux中运行一下,运行界面如图所示: 从上图中可以看到两处字符串,上面的字符串不断滚动,下方字符串在次数上不断累加,猜测上方字符 ...

  2. Vue学习笔记-指令

  3. 文心一言 VS 讯飞星火 VS chatgpt (23)-- 算法导论4.2 5题

    五.V.Pan 发现一种方法,可以用 132 464 次乘法操作完成 68 x 68 的矩阵相乘,发现另一种方法,可以用 143 640 次乘法操作完成 70 x 70 的矩阵相乘,还发现一种方法,可 ...

  4. 记录:websoket切换页面后重复执行问题

    问题描述 因为项目需求,实时播放执行信息.而项目的websoket只在这个页面,会有切换情况.从websoket连接得到执行列表数据.断开重连后会传递新连接数据+旧连接数据.也就是说,如果第一次进入页 ...

  5. Python图像处理丨如何调用OpenCV绘制直方图

    摘要:本篇文章主要讲解灰度直方图的基本概念,Python调用OpenCV实现绘制图像直方图. 本文分享自华为云社区<[Python图像处理] 十一.灰度直方图概念及OpenCV绘制直方图> ...

  6. 通过windows自带管理工具、系统命令行、快捷键等快速操作

    windows自带管理工具 我们win+R 输入一些命令,可以快速打开一些界面,比如: sysdm.cpl win10.win11 我电脑,属性与之前win7不同了,我希望打开之前的属性打不开了 通过 ...

  7. vue2升级vue3: Event Bus 替代方案

    在看 https://v3-migration.vuejs.org/breaking-changes/events-api.html 在vue2里面 In 2.x, a Vue instance co ...

  8. Kubernetes(K8S) 镜像拉取策略 imagePullPolicy

    镜像仓库,镜像已更新,版本没更新, K8S 拉取后,还是早的服务,原因:imagePullPolicy 镜像拉取策略 默认为本地有了就不拉取,需要修改 [root@k8smaster ~]# kube ...

  9. Java 网络编程 —— 实现非阻塞式的客户端

    创建阻塞的 EchoClient 客户程序一般不需要同时建立与服务器的多个连接,因此用一个线程,按照阻塞模式运行就能满足需求 public class EchoClient { private Soc ...

  10. esp8266 水墨屏显示中文之简单字库

    esp8266 驱动水墨屏显示中文,假设在此之前已经安装好arduino,配置好esp8266的开发环境.水墨屏显示中文的步骤如下: 一.下载库 安装esp8266需要的库文件: GxEPD2 屏幕驱 ...