作业 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. 【UniApp】-uni-app-项目实战页面布局(苹果计算器)

    前言 经过前面的文章介绍,基本上 UniApp 的内容就介绍完毕了 那么从本文开始,我们就开始进行一个项目的实战 这次做的项目是苹果计算器,这个项目的难度不是很大,但是也不是很简单,适合练手 创建项目 ...

  2. 自定义开发odoo14的统计在线用户人数

    在 Odoo 14 中统计在线人数通常涉及到定制开发或者使用特定的模块. 自定义开发:如果没有现成的模块,您可能需要进行一些自定义开发.这通常涉及到扩展Odoo的用户模型,以跟踪用户的登录和登出活动. ...

  3. 三种方式MD5加密

    1.直接生成(标准的MD5加密) 选择函数助手,选择__digest,填写算法和密码,点击生成 2.开发直接给的jar包 步骤:a.从测试计划导入jar包 或 放在jmeter的lib\ext目录读取 ...

  4. ASR项目实战-构建Kaldi

    准备工作 安装构建时依赖的基础软件 软件清单如下: bzip2 python3 automake libtool cmake gcc g++ gfortran git subversion 不同平台安 ...

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

    三.用go语言,考虑除法散列法的另一种版本,其中 h(k) = k mod m,m=$2^p-1$,k为按基数 $2^p$ 表示的字符串.试证明:如果串可由串 y 通过其自身的字符置换排列导出,则x和 ...

  6. 27、flutter Dialog 弹窗

    AlertDialog //放在State<>之下 void _alertDialog() async { var result = await showDialog( barrierDi ...

  7. 10、弹性布局(Flex Expanded)

    自定义的IconContainer class IconContainer extends StatelessWidget { Color color; IconData icon; // IconC ...

  8. 云图说 | 华为云MCP多云容器平台,让您轻松灾备!

    摘要:多云容器平台是华为云基于多年容器云领域实践经验和社区先进的集群联邦技术,提供的容器多云和混合云的解决方案. 多云容器平台(Multi-Cloud Container Platform,MCP)是 ...

  9. 实战案例丨分布式系统中如何用python实现Paxos

    摘要:提到分布式算法,就不得不提 Paxos 算法,在过去几十年里,它基本上是分布式共识的代 名词,因为当前最常用的一批共识算法都是基于它改进的.比如,Fast Paxos 算法. Cheap Pax ...

  10. 13个VSCode使用技巧,开启高效的开发模式

    摘要:VsCode是一款开源的编辑器,拥有强大的功能,.由于拥有各种各样的插件,这就使得VsCode可以做到的事情更多了.在使用的过程中,也是有很多技巧的,掌握一些技巧对于后期写代码也会轻松很多. 本 ...