【静态分析】软件分析课程实验A3-死代码检测

官网:

作业 3:死代码检测 | Tai-e

参考:

https://www.cnblogs.com/gonghr/p/17981720

---------------------------------------------------------------------

1 作业导览

  • 为 Java 实现一个死代码(dead code)检测算法。

从程序中去除死代码是一种常见的编译优化策略。其中最困难的问题是如何检测到程序中的死代码。在这次的实验作业中,你将会通过组合你前两次作业中实现的分析方法:活跃变量分析和常量传播,来实现一个 Java 的死代码检测算法。在本文档中,我们将会明确界定本次作业中所讨论的死代码的范畴,你的任务就是实现一个检测算法识别它们。

idea打开实验作业仓库的 A3/tai-e/,并按【静态分析】软件分析课程实验-前置准备-CSDN博客进行配置。

2 死代码检测介绍

死代码指的是程序中不可达的(unreachable)代码(即不会被执行的代码),或者是执行结果永远不会被其他计算过程用到的代码。去除死代码可以在不影响程序输出的前提下简化程序、提高效率。在本次作业中,我们只关注两种死代码:不可达代码(unreachable code)和无用赋值(dead assignment)。

2.1 不可达代码

一个程序中永远不可能被执行的代码被称为不可达代码。我们考虑两种不可达代码:控制流不可达代码(control-flow unreachable code)和分支不可达代码(unreachable branch)。这两种代码的介绍如下。

控制流不可达代码. 在一个方法中,如果不存在从程序入口到达某一段代码的控制流路径,那么这一段代码就是控制流不可达的。比如,由于返回语句是一个方法的出口,所以跟在它后面的代码是不可达的。例如在下面的代码中,第 4 行和第 5 行的代码是控制流不可达的:

int controlFlowUnreachable() {int x = 1;return x;int z = 42; // control-flow unreachable codefoo(z); // control-flow unreachable code
}

检测方式:这样的代码可以很简单地利用所在方法的控制流图(CFG,即 control-flow graph)检测出来。我们只需要从方法入口开始,遍历 CFG 并标记可达语句。当遍历结束时,那些没有被标记的语句就是控制流不可达的。

分支不可达代码. 在 Java 中有两种分支语句:if 语句和 switch 语句。它们可能会导致分支不可达代码的出现。

对于一个 if 语句,如果它的条件值(通过常量传播得知)是一个常数,那么无论程序怎么执行,它两个分支中的其中一个分支都不会被走到。这样的分支被称为不可达分支。该分支下的代码也因此是不可达的,被称为分支不可达代码。如下面的代码片段所示,由于第 3 行 if 语句的条件是永真的,所以它条件为假时对应的分支为不可达分支,该分支下的代码(第 6 行)是分支不可达代码。

int unreachableIfBranch() {int a = 1, b = 0, c;if (a > b)c = 2333;elsec = 6666; // unreachable branchreturn c;
}

对于一个 switch 语句,如果它的条件值是一个常数,那么不符合条件值的 case 分支就可能是不可达的。如下面的代码片段所示,第 3 行 switch 语句的条件值(变量 x 的值)永远是 2 ,因此分支 “case 1” 和 “default” 是不可达的。注意,尽管分支 “case 3” 同样没法匹配上条件值(也就是 2),但它依旧是可达的,因为控制流可以从分支 “case 2” 流到它。

int unreachableSwitchBranch() {int x = 2, y;switch (x) {case 1: y = 100; break; // unreachable branchcase 2: y = 200;case 3: y = 300; break; // fall throughdefault: y = 666; // unreachable branch}return y;
}

检测方式:为了检测分支不可达代码,我们需要预先对被检测代码应用常量传播分析,通过它来告诉我们条件值是否为常量,然后在遍历 CFG 时,我们不进入相应的不可达分支。

2.2 无用赋值

一个局部变量在一条语句中被赋值,但再也没有被该语句后面的语句读取,这样的变量和语句分别被称为无用变量(dead variable,与活跃变量 live variable 相对)和无用赋值。无用赋值不会影响程序的输出,因而可以被去除。如下面的代码片段所示,第 3 行和第 5 行的语句都是无用赋值。

int deadAssign() {int a, b, c;a = 0; // dead assignmenta = 1;b = a * 2; // dead assignmentc = 3;return c;
}

检测方式:为了检测无用赋值,我们需要预先对被检测代码施用活跃变量分析。对于一个赋值语句,如果它等号左侧的变量(LHS 变量)是一个无用变量(换句话说,not live),那么我们可以把它标记为一个无用赋值。

但需要注意的是,以上讨论有一种例外情况:有时即使等号左边的变量 x 是无用变量,它所属的赋值语句 x = expr 也不能被去除,因为右边的表达式 expr 可能带有某些副作用。例如,当 expr 是一个方法调用(x = m())时,它就有可能带有副作用。对于这种情况,我们提供了一个 API 供你检查等号右边的表达式是否可能带有副作用(在第 3.2 节说明)。如果带有副作用,那么为了保证 safety,即使 x 不是一个活跃变量,你也不应该把这个赋值语句标记为死代码。

3 实现死代码检测器

3.1 Tai-e 中你需要了解的类

为了实现死代码检测算法,你需要知道 CFG,IR,还有其他与活跃变量分析、常量传播分析结果有关的类(比如 CPFact,DataflowResult 等),不过你已经在之前的作业中使用过了它们,应该对它们很熟悉了!接下来我们介绍更多本次作业中将会用到的和 CFG 以及 IR 有关的类。

  • pascal.taie.analysis.graph.cfg.Edge

    这个类表示 CFG 中的边(提示:CFG 中的节点是 Stmt)。它具有方法 getKind(),可以用来得知某个边的种类(你可以通过阅读类 Edge.Kind 的注释来理解各个种类的含义),并且你可以像下面这样检查边的种类:

    Edge<Stmt> edge = ...;
    if (edge.getKind() == Edge.Kind.IF_TRUE) { ... }
    

在这次作业中,你需要考虑四种边:IF_TRUE、IF_FALSE、SWITCH_CASE 和 SWITCH_DEFAULT。IF_TRUE 和 IF_FALSE 表示从 if 语句到它的两个分支的出边,就像下面的例子所示:

SWITCH_CASE 和 SWITCH_DEFAULT 表示从 switch 语句到它的 case 分支和 default 分支的出边,就像下面的例子所示:

对于 SWITCH_CASE 边,你可以通过 getCaseValue() 方法来获取它们对应的 case 分支的条件值(比如在上面的例子中,调用 case 1 对应的出边的 getCaseValue() 方法会返回值 1,调用 case 3 对应的 out edge 的 getCaseValue() 方法会返回值 3)。

pascal.taie.ir.stmt.If(Stmt 的子类)

这个类表示程序中的 if 语句。

值得注意的是,在 Tai-e 的 IR 中,while 循环和 for 循环也被转换成了 If 语句。比如下面这个用 Java 写的循环:

while (a > b) {x = 233;
}
y = 666;

在 Tai-e 中将会被转化成像这样的 IR:

0:  if (a > b) goto 2;
1:  goto 4;
2:  x = 233;
3:  goto 0;
4:  y = 666;

因此,你的算法实现不需多加改变就能自然而然地支持检测与循环相关的死代码。比如,如果 a 和 b 都是常量并且 a <= b,那么你的分析算法应该把语句 x = 233 标记成死代码。

  • pascal.taie.ir.stmt.SwitchStmt(Stmt 的子类)

    这个类表示程序中的 switch 语句。你需要阅读它的源代码和注释来决定如何使用它。

  • pascal.taie.ir.stmt.AssignStmt(Stmt 的子类)

    这个类表示程序中的赋值语句(比如 x = ...;)。你可能会觉得它有点像你之前看到过的 DefinitionStmt。下面的部分的类继承关系图展示了这两个类的关系:

  • 事实上,AssignStmt 是 DefinitionStmt 两个子类的其中一个(另一个是 Invoke,它表示程序中的方法调用)。这意味着除了等号右侧是方法调用的赋值语句,其他赋值语句都用 AssignStmt 表示。正如第 2.2 节所说的,方法调用可能含有很多副作用,因此对于像 x = m(); 这样的语句,即使 x 之后再也不会被用到(换言之,x 是无用变量),这条语句也不会被认为是无用赋值。因此,本次作业中所有可能的无用赋值都只可能是 AssignStmt 的实例。你只需要关注 AssignStmt 这个类即可。

  • pascal.taie.analysis.dataflow.analysis.DeadCodeDetection

    这个类是实现死代码检测的类。你需要根据第 3.2 节的指导来补完它。

3.2 你的任务 [重点!]

你需要完成 DeadCodeDetection 中的一个API:

  • Set<Stmt> analyze(IR)

这个方法将一个 IR 作为输入,返回一个包含 IR 中死代码的集合。你的任务是找出第 2 节中描述的两种死代码(也就是不可达代码和无用赋值),然后将它们加入到作为结果返回的集合中。 为了简单起见,你不需要考虑由删除死代码而产生的新的死代码。就拿我们前面在介绍无用赋值时用过的例子来说,当下列代码中第 3 行和第 5 行的无用赋值被删除后,第 4 行的 a = 1 会变成新的无用赋值,只不过在本次作业中,你不必把它识别为死代码(即不加入到结果集中)。

int deadAssign() {int a, b, c;a = 0; // dead assignmenta = 1;b = a * 2; // dead assignmentc = 3;return c;
}

死代码检测依赖活跃变量分析和常量传播分析的结果。因此,为了让死代码检测能跑起来,你需要先补全 LiveVariableAnalysis.java 和 ConstantPropagation.java。你可以拷贝你之前作业中的实现。另外,你也需要完成一个同时支持前向分析和后向分析的 worklist 求解器。你可以从作业 2 中拷贝你之前对 Solver.java 和 WorkListSolver.java 的实现,并在这次作业中实现 Solver.initializeBackward()和 WorkListSolver.doSolveBackward()。不过不用担心,这次作业中我们不会要求你提交这些代码的源文件,所以即使你之前作业中的实现并不是完全正确的,它们也不会影响你本次作业的分数。

/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.analysis;import pascal.taie.analysis.dataflow.fact.SetFact;
import pascal.taie.analysis.graph.cfg.CFG;
import pascal.taie.config.AnalysisConfig;
import pascal.taie.ir.exp.LValue;
import pascal.taie.ir.exp.RValue;
import pascal.taie.ir.exp.Var;
import pascal.taie.ir.stmt.Stmt;import java.util.List;
import java.util.Optional;/*** Implementation of classic live variable analysis.*/
public class LiveVariableAnalysis extendsAbstractDataflowAnalysis<Stmt, SetFact<Var>> {public static final String ID = "livevar";public LiveVariableAnalysis(AnalysisConfig config) {super(config);}@Overridepublic boolean isForward() {return false;}@Overridepublic SetFact<Var> newBoundaryFact(CFG<Stmt> cfg) {// TODO - finish mereturn new SetFact<>();}@Overridepublic SetFact<Var> newInitialFact() {// TODO - finish mereturn new SetFact<>();}@Overridepublic void meetInto(SetFact<Var> fact, SetFact<Var> target) {// TODO - finish metarget.union(fact);}@Overridepublic boolean transferNode(Stmt stmt, SetFact<Var> in, SetFact<Var> out) {// TODO - finish meOptional<LValue> def = stmt.getDef();List<RValue> uses = stmt.getUses();SetFact<Var> newSetFact = new SetFact<>();newSetFact.union(out);if(def.isPresent()) {if(def.get() instanceof Var) {newSetFact.remove((Var) def.get());}}for (RValue use : uses) {if (use instanceof Var) {newSetFact.add((Var) use);}}if (!in.equals(newSetFact)) {in.set(newSetFact);return true;}return false;}
}
/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.analysis.constprop;import pascal.taie.analysis.dataflow.analysis.AbstractDataflowAnalysis;
import pascal.taie.analysis.graph.cfg.CFG;
import pascal.taie.config.AnalysisConfig;
import pascal.taie.ir.IR;
import pascal.taie.ir.exp.*;
import pascal.taie.ir.stmt.DefinitionStmt;
import pascal.taie.ir.stmt.Stmt;
import pascal.taie.language.type.PrimitiveType;
import pascal.taie.language.type.Type;
import pascal.taie.util.AnalysisException;public class ConstantPropagation extendsAbstractDataflowAnalysis<Stmt, CPFact> {public static final String ID = "constprop";public ConstantPropagation(AnalysisConfig config) {super(config);}@Overridepublic boolean isForward() {return true;}@Overridepublic CPFact newBoundaryFact(CFG<Stmt> cfg) {// TODO - finish meCPFact cpFact = new CPFact();for (Var param : cfg.getIR().getParams()) {if (canHoldInt(param)) {                   // 只考虑可转换int类型的参数cpFact.update(param, Value.getNAC());  // 建立参数到格上值(NAC)的映射}}return cpFact;}@Overridepublic CPFact newInitialFact() {// TODO - finish mereturn new CPFact();}@Overridepublic void meetInto(CPFact fact, CPFact target) {// TODO - finish mefor (Var var : fact.keySet()) {Value v1 = fact.get(var);Value v2 = target.get(var);target.update(var, meetValue(v1, v2));}}/*** Meets two Values.*/public Value meetValue(Value v1, Value v2) {// TODO - finish meif (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();}}@Overridepublic boolean transferNode(Stmt stmt, CPFact in, CPFact out) {// TODO - finish meCPFact 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。有更新,返回true;反之返回false}/*** @return true if the given variable can hold integer value, otherwise false.*/public static boolean canHoldInt(Var var) {Type type = var.getType();if (type instanceof PrimitiveType) {switch ((PrimitiveType) type) {case BYTE:case SHORT:case INT:case CHAR:case BOOLEAN:return true;}}return false;}/*** 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 meif (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()) {if (v1.isNAC() && v2.isConstant() && exp instanceof ArithmeticExp) {  // x = a / 0,x = a % 0,x 的值将会是 UNDEFArithmeticExp.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 {  // 二元表达式中的其他类型表达式return Value.getNAC();}}return Value.getNAC();}
}
/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.solver;import pascal.taie.analysis.dataflow.analysis.DataflowAnalysis;
import pascal.taie.analysis.dataflow.fact.DataflowResult;
import pascal.taie.analysis.graph.cfg.CFG;/*** Base class for data-flow analysis solver, which provides common* functionalities for different solver implementations.** @param <Node> type of CFG nodes* @param <Fact> type of data-flow facts*/
public abstract class Solver<Node, Fact> {protected final DataflowAnalysis<Node, Fact> analysis;protected Solver(DataflowAnalysis<Node, Fact> analysis) {this.analysis = analysis;}/*** Static factory method to create a new solver for given analysis.*/public static <Node, Fact> Solver<Node, Fact> makeSolver(DataflowAnalysis<Node, Fact> analysis) {return new WorkListSolver<>(analysis);}/*** Starts this solver on the given CFG.** @param cfg control-flow graph where the analysis is performed on* @return the analysis result*/public DataflowResult<Node, Fact> solve(CFG<Node> cfg) {DataflowResult<Node, Fact> result = initialize(cfg);doSolve(cfg, result);return result;}/*** Creates and initializes a new data-flow result for given CFG.** @return the initialized data-flow result*/private DataflowResult<Node, Fact> initialize(CFG<Node> cfg) {DataflowResult<Node, Fact> result = new DataflowResult<>();if (analysis.isForward()) {initializeForward(cfg, result);} else {initializeBackward(cfg, result);}return result;}protected void initializeForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish meresult.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());}}protected void initializeBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish me// Init Exit noderesult.setInFact(cfg.getExit(), analysis.newBoundaryFact(cfg));// Init other nodesfor (Node node : cfg) {if (!cfg.isExit(node)) {result.setInFact(node, analysis.newInitialFact());result.setOutFact(node, analysis.newInitialFact());}}}/*** Solves the data-flow problem for given CFG.*/private void doSolve(CFG<Node> cfg, DataflowResult<Node, Fact> result) {if (analysis.isForward()) {doSolveForward(cfg, result);} else {doSolveBackward(cfg, result);}}protected abstract void doSolveForward(CFG<Node> cfg, DataflowResult<Node, Fact> result);protected abstract void doSolveBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result);
}
/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.solver;import pascal.taie.analysis.dataflow.analysis.DataflowAnalysis;
import pascal.taie.analysis.dataflow.fact.DataflowResult;
import pascal.taie.analysis.graph.cfg.CFG;import java.util.ArrayDeque;class WorkListSolver<Node, Fact> extends Solver<Node, Fact> {WorkListSolver(DataflowAnalysis<Node, Fact> analysis) {super(analysis);}@Overrideprotected void doSolveForward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish me// 向下分析,对于块B,通过IN[B]计算OUT[B], IN[B]是前驱OUT的处理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(may analysis)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);}}}}@Overrideprotected void doSolveBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {// TODO - finish me// 向上分析,通过OUT[B]计算IN[B], OUT[B]是后继IN的处理ArrayDeque<Node> worklist = new ArrayDeque<>();for (Node node : cfg) {if (cfg.isExit(node)) {continue;}worklist.addFirst(node);}while (!worklist.isEmpty()) {Node node = worklist.pollFirst();for (Node succ : cfg.getSuccsOf(node)) {  // 求B的OUTanalysis.meetInto(result.getInFact(succ), result.getOutFact(node));  // may analysis}boolean f = analysis.transferNode(node, result.getInFact(node), result.getOutFact(node));if (f) {for (Node pred : cfg.getPredsOf(node)) {worklist.addFirst(pred);}}}}
}

提示:

  1. 在这次作业中,Tai-e 会在运行死代码检测之前自动运行活跃变量分析和常量传播分析。我们在 DeadCodeDetection.analyze() 中提供了用来获得这两种分析算法针对目标 IR 的分析结果,这样你可以直接使用它们。另外,analyze() 方法包含获取 IR 的 CFG 的代码。
  2. 正如第 2.2 节提到的那样,某些赋值语句等号右侧的表达式可能含有副作用,因此不能被当作 dead assignments。我们在 DeadCodeDetection 中提供了一个辅助方法 hasNoSideEffect(RValue),用来帮助你检查一个表达式是否含有副作用。
  3. 在遍历 CFG 时,你需要对当前正在访问的节点使用 CFG.getOutEdgesOf() 来帮助获得之后要被访问的后继节点。这个 API 返回给定节点在 CFG 上的出边,所以你可以用边的信息(在第 3.1 节介绍过)来帮助找出分支不可达代码。
  4. 当在寻找分支不可达代码时,你可以使用 ConstantPropagation.evaluate() 来计算 if 和 switch 语句的条件值。

整体思路:

记录所有可达的语句,没有被记录的语句都是不可达的死代码。

不能直接遍历控制流图中的 IR ,这些 IR 有可能是不可达的,而是使用队列(stmts)记录所有即将被访问的语句,进行遍历,对于每条语句根据其不同的类型进行不同处理。

除了队列以外,还需要一个集合(reached)来判断某条语句是否访问过,避免重复访问,防止死循环。

最后使用一个集合(reachable)记录哪些语句是可达的(语句先访问再判断是否可达)。

有几个易错点:

  • AssignStmt 处理时,左值要先判断能否转成成 Var ,防止类型转换异常。
  • SwitchStmt 处理时,如果所有 case 都匹配不到,可能会执行 default 中控制流。

新语法知识:

在Java 14及更高版本中,可以使用" Pattern Matching for instanceof "特性来将 instanceof 的结果转化并赋值给一个新的变量。这种语法可以简化类型判断和类型转换的代码。注意,被转换的对象必须是final或effectively final的,以确保转换后的变量是不可变的。此外,这种语法只适用于局部变量,不能用于成员变量或静态变量的赋值。


if (obj instanceof MyClass myObj) {// 将obj转换为MyClass类型,并赋值给myObj变量// 可以在if语句的代码块中使用myObjmyObj.doSomething();
} else {// obj不是MyClass类型的处理逻辑// ...
}
/** Tai-e: A Static Analysis Framework for Java** Copyright (C) 2022 Tian Tan <tiantan@nju.edu.cn>* Copyright (C) 2022 Yue Li <yueli@nju.edu.cn>** This file is part of Tai-e.** Tai-e is free software: you can redistribute it and/or modify* it under the terms of the GNU Lesser General Public License* as published by the Free Software Foundation, either version 3* of the License, or (at your option) any later version.** Tai-e is distributed in the hope that it will be useful,but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General* Public License for more details.** You should have received a copy of the GNU Lesser General Public* License along with Tai-e. If not, see <https://www.gnu.org/licenses/>.*/package pascal.taie.analysis.dataflow.analysis;import pascal.taie.analysis.MethodAnalysis;
import pascal.taie.analysis.dataflow.analysis.constprop.CPFact;
import pascal.taie.analysis.dataflow.analysis.constprop.ConstantPropagation;
import pascal.taie.analysis.dataflow.analysis.constprop.Value;
import pascal.taie.analysis.dataflow.fact.DataflowResult;
import pascal.taie.analysis.dataflow.fact.SetFact;
import pascal.taie.analysis.graph.cfg.CFG;
import pascal.taie.analysis.graph.cfg.CFGBuilder;
import pascal.taie.analysis.graph.cfg.Edge;
import pascal.taie.config.AnalysisConfig;
import pascal.taie.ir.IR;
import pascal.taie.ir.exp.ArithmeticExp;
import pascal.taie.ir.exp.ArrayAccess;
import pascal.taie.ir.exp.CastExp;
import pascal.taie.ir.exp.FieldAccess;
import pascal.taie.ir.exp.NewExp;
import pascal.taie.ir.exp.RValue;
import pascal.taie.ir.exp.Var;
import pascal.taie.ir.stmt.AssignStmt;
import pascal.taie.ir.stmt.If;
import pascal.taie.ir.stmt.Stmt;
import pascal.taie.ir.stmt.SwitchStmt;import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;import pascal.taie.ir.exp.*;
import java.util.*;public class DeadCodeDetection extends MethodAnalysis {public static final String ID = "deadcode";public DeadCodeDetection(AnalysisConfig config) {super(config);}@Overridepublic Set<Stmt> analyze(IR ir) {// obtain CFGCFG<Stmt> cfg = ir.getResult(CFGBuilder.ID);// obtain result of constant propagationDataflowResult<Stmt, CPFact> constants =ir.getResult(ConstantPropagation.ID);// obtain result of live variable analysisDataflowResult<Stmt, SetFact<Var>> liveVars =ir.getResult(LiveVariableAnalysis.ID);// keep statements (dead code) sorted in the resulting setSet<Stmt> deadCode = new TreeSet<>(Comparator.comparing(Stmt::getIndex));// TODO - finish me// Your task is to recognize dead code in ir and add it to deadCode// 检测控制流不可达代码、分支不可达和无用赋值ArrayDeque<Stmt> stmts = new ArrayDeque<>();  // 队列Set<Stmt> reachable = new HashSet<>();Set<Stmt> reached = new HashSet<>();stmts.addLast(cfg.getEntry());  // 第一个访问结点是方法的入口reachable.add(cfg.getExit());   // 方法的入口和出口肯定是可达的reachable.add(cfg.getEntry());while (!stmts.isEmpty()) {Stmt stmt = stmts.pollFirst();  // 弹出队头reached.add(stmt);  // 记录弹出结点被访问// 无用赋值语句处理,本次作业中所有可能的无用赋值都只可能是 AssignStmt 的实例if (stmt instanceof AssignStmt assignStmt) {SetFact<Var> liveVarsResult = liveVars.getResult(assignStmt);  // 获取当前语句执行后的活跃变量结果LValue lValue = assignStmt.getLValue();  // 获取当前语句的左值RValue rValue = assignStmt.getRValue();  // 获取当前语句的右值boolean f = true;  // 左值是死变量,右值没有side effect的语句是死代码if (lValue instanceof Var) {  // 易错点1:要判断左值是否能转化成变量类型if (!liveVarsResult.contains((Var) lValue)) {  // deadif (hasNoSideEffect(rValue)) {  // no side effectf = false;}}}if (f) {  // 如果不是特殊情况,那么当前语句可达reachable.add(assignStmt);}for (Stmt succ : cfg.getSuccsOf(assignStmt)) {  // 后继结点加入队列if (!reached.contains(succ))stmts.addLast(succ);}} else if (stmt instanceof If ifStmt) {  // if语句处理CPFact result = constants.getResult(ifStmt);  // 获取常量传播结果ConditionExp condition = ifStmt.getCondition(); // 获取if条件表达式ConditionExp.Op operator = condition.getOperator(); // 获取运算符Value evaluate = ConstantPropagation.evaluate(condition, result); // 计算if条件表达式reachable.add(ifStmt);  // 当前if语句可达if (evaluate.isConstant()) {  // 如果if条件表达式是个常数,那么只可能到达一个分支if (evaluate.getConstant() == 1) {  // 永远truefor (Edge<Stmt> edge : cfg.getOutEdgesOf(ifStmt)) {  // 所有出边if (edge.getKind() == Edge.Kind.IF_TRUE) {  // true边一定到Stmt target = edge.getTarget();if (!reached.contains(target))stmts.add(target);  // 目标结点添加到队列}}} else {  // 永远falsefor (Edge<Stmt> edge : cfg.getOutEdgesOf(stmt)) {  // 所有出边if (edge.getKind() == Edge.Kind.IF_FALSE) {  // false边一定到Stmt target = edge.getTarget();if (!reached.contains(target))stmts.add(target);  // 目标节点添加到队列}}}} else {  // 如果if条件表达式不是个常数,那么两条分支都可能,按照控制流执行for (Stmt succ : cfg.getSuccsOf(stmt)) {if (!reached.contains(succ))stmts.addLast(succ);}}} else if (stmt instanceof SwitchStmt switchStmt) {  // switch语句处理Var var = switchStmt.getVar();  // 获取switch表达式的变量CPFact result = constants.getResult(switchStmt);  // 获取常量传播结果reachable.add(switchStmt);  // 当前switch语句可达if (result.get(var).isConstant()) {  // 如果switch表达式是常数,只可能到达几个分支int constant = result.get(var).getConstant();  // 获取表达式的常量值boolean match = false;  // 易错点2:记录是否匹配case,如果没有,将执行defaultfor (Edge<Stmt> edge : cfg.getOutEdgesOf(switchStmt)) {  // 获取所有出边if (edge.getKind() == Edge.Kind.SWITCH_CASE) {  // 如果是case类型边int caseValue = edge.getCaseValue();  // 获取case值if (caseValue == constant) {   // 如果是匹配的casematch = true;if (!reached.contains(edge.getTarget()))stmts.addLast(edge.getTarget());}}}if (!match) {  // 如果不匹配,执行defaultStmt defaultTarget = switchStmt.getDefaultTarget();  // 获取default对应的目标语句if (!reached.contains(defaultTarget))stmts.addLast(defaultTarget);}} else {  // 如果switch表达式不是常数,每个case都可能执行,按照控制流执行for (Stmt succ : cfg.getSuccsOf(switchStmt)) {if (!reached.contains(succ))stmts.addLast(succ);}}} else {  // 执行到的其他类型语句,按照控制流执行reachable.add(stmt);for (Stmt succ : cfg.getSuccsOf(stmt)) {if (!reached.contains(succ))stmts.addLast(succ);}}}for (Stmt stmt : ir.getStmts()) {  // 遍历当前方法的所有IR,如果不可达,那么就是死代码if (!reachable.contains(stmt)) {deadCode.add(stmt);}}return deadCode;}/*** @return true if given RValue has no side effect, otherwise false.*/private static boolean hasNoSideEffect(RValue rvalue) {// new expression modifies the heapif (rvalue instanceof NewExp ||// cast may trigger ClassCastExceptionrvalue instanceof CastExp ||// static field access may trigger class initialization// instance field access may trigger NPErvalue instanceof FieldAccess ||// array access may trigger NPErvalue instanceof ArrayAccess) {return false;}if (rvalue instanceof ArithmeticExp) {ArithmeticExp.Op op = ((ArithmeticExp) rvalue).getOperator();// may trigger DivideByZeroExceptionreturn op != ArithmeticExp.Op.DIV && op != ArithmeticExp.Op.REM;}return true;}
}

4 运行与测试

你可以参考 Tai-e 框架(教学版)配置指南 来运行分析算法。在这次作业中,Tai-e 为输入的类中的每一个方法运行活跃变量分析、常量传播分析和死代码检测算法。为了帮助调试,它会如下输出三个分析算法的结果:

--------------------<DeadAssignment: void deadAssign()> (livevar)--------------------

[0@L4] x = 1; null

[1@L5] %intconst0 = 2; null

[2@L5] y = x + %intconst0; null

[3@L6] %intconst1 = 3; null

[4@L6] z = x + %intconst1; null

[5@L7] invokevirtual %this.<DeadAssignment: void use(int)>(z); null

[6@L8] a = x; null

[7@L8] return; null

--------------------<DeadAssignment: void deadAssign()> (constprop)--------------------

[0@L4] x = 1; null

[1@L5] %intconst0 = 2; null

[2@L5] y = x + %intconst0; null

[3@L6] %intconst1 = 3; null

[4@L6] z = x + %intconst1; null

[5@L7] invokevirtual %this.<DeadAssignment: void use(int)>(z); null

[6@L8] a = x; null

[7@L8] return; null

--------------------<DeadAssignment: void deadAssign()> (deadcode)--------------------

当未完成这三个分析算法的时候,OUT facts 都为 null,并且没有代码被标记为死代码。在你完成了三个分析算法后,输出应当形如:

--------------------<DeadAssignment: void deadAssign()> (livevar)--------------------

[0@L4] x = 1; [%this, x]

[1@L5] %intconst0 = 2; [%intconst0, %this, x]

[2@L5] y = x + %intconst0; [%this, x]

[3@L6] %intconst1 = 3; [%intconst1, %this, x]

[4@L6] z = x + %intconst1; [%this, x, z]

[5@L7] invokevirtual %this.<DeadAssignment: void use(int)>(z); [x]

[6@L8] a = x; []

[7@L8] return; []

--------------------<DeadAssignment: void deadAssign()> (constprop)--------------------

[0@L4] x = 1; {x=1}

[1@L5] %intconst0 = 2; {%intconst0=2, x=1}

[2@L5] y = x + %intconst0; {%intconst0=2, x=1, y=3}

[3@L6] %intconst1 = 3; {%intconst0=2, %intconst1=3, x=1, y=3}

[4@L6] z = x + %intconst1; {%intconst0=2, %intconst1=3, x=1, y=3, z=4}

[5@L7] invokevirtual %this.<DeadAssignment: void use(int)>(z); {%intconst0=2, %intconst1=3, x=1, y=3, z=4}

[6@L8] a = x; {%intconst0=2, %intconst1=3, a=1, x=1, y=3, z=4}

[7@L8] return; {%intconst0=2, %intconst1=3, a=1, x=1, y=3, z=4}

--------------------<DeadAssignment: void deadAssign()> (deadcode)--------------------

[2@L5] y = x + %intconst0;

[6@L8] a = x;

此外,Tai-e 会把它分析的目标方法的控制流图输出到文件夹 output/ 里。CFGs 会被存储成 .dot 文件,并且可以通过 Graphviz

可视化。

我们为这次作业提供了测试驱动 pascal.taie.analysis.dataflow.analysis.DeadCodeTest。你可以按照 Tai-e 框架(教学版)配置指南 所介绍的那样使用它来测试你的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/pingmian/9690.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

驱动开发-用户空间和内核空间数据传输

1.用户空间-->内核空间&#xff08;写&#xff09; #include<linux/uaccess.h> int copy_from_user(void *to,const void __user volatile*from,unsigned long n) 函数功能&#xff1a;将用户空间数据拷贝到内核空间 参数&#xff1a; to&#xff1a;内核空间首地…

【计算机毕业设计】springboot果蔬种植销售一体化服务平台

伴随着我国社会的发展&#xff0c;人民生活质量日益提高。于是对果蔬种植销售一体化服务管理进行规范而严格是十分有必要的&#xff0c;所以许许多多的 信息管理系统应运而生。此时单靠人力应对这些事务就显得有些力不从心了。所以本论文将设计一套果蔬种植销售一体化服务平台&…

RS2255XN功能和参数介绍及PDF资料

RS2255XN是一款由Runic&#xff08;润石&#xff09;公司生产的模拟开关。以下是关于RS2255XN的一些技术参数和特点&#xff1a; 封装&#xff1a;MSOP-10 电源电压范围&#xff1a;2.5V至5.5V 工作温度范围&#xff1a;-40C至125C 类型&#xff1a;模拟开关 品牌&#xff1a;R…

Android之使ImageView中图片保持比例显示,且不超过最大高度和最大宽度限制

1、原来的&#xff0c;没有大小尺寸限制&#xff0c;不重要 <ImageView android:id"id/img_one_show" android:layout_width"wrap_content" android:layout_height"wrap_content" android:scaleType"fitStart" android:visibil…

如何使用Whisper音频合成模型

Whisper 是一个通用语音识别模型&#xff0c;由 OpenAI 开发。它可以识别多种语言的语音&#xff0c;并将其转换为文本。Whisper 模型采用了深度学习技术&#xff0c;具有高准确性和鲁棒性。 1、技术原理及架构 Whisper 的工作原理&#xff1a;音频被分割成 30 秒的片段&#…

云计算导论(2)---云计算基础

文章目录 1. 分布式计算2. 分布式计算系统架构3. 分布式计算关键技术4. 分布式计算性能优化方法5. 云计算的基本概念6. 云计算的关键技术 1. 分布式计算 1. 定义&#xff1a;分布式计算是一种计算方法&#xff0c;将一个大型任务拆分成多个小任务&#xff0c;并分配给多台计算机…

c#绘制渐变色的Led

项目场景&#xff1a; c#绘制渐变色的button using System; using System.ComponentModel; using System.Drawing; using System.Drawing.Drawing2D; using System.Windows.Forms; using static System.Windows.Forms.AxHost;namespace WindowsFormsApp2 {public class Gradie…

【C++】-类模板-002

1创建类模板 &#xff08;1&#xff09;新建工程 &#xff08;2&#xff09; &#xff08;3&#xff09; &#xff08;4&#xff09; &#xff08;5&#xff09;模板运行结果 2【UI】设计器 &#xff08;1&#xff09;跳转到【UI】设计器 &#xff08;2&#xff09;添加…

纯血鸿蒙APP实战开发——一镜到底“页面转场”动画

介绍 本方案做的是页面点击卡片跳转到详情预览的转场动画效果 效果图预览 使用说明 点击首页卡片跳转到详情页&#xff0c;再点击进入路由页面按钮&#xff0c;进入新的路由页面 实现思路 首页使用了一种视觉上看起来像是组件的转场动画&#xff0c;这种转场动画通常是通过…

教你解决PUBG绝地求生打完一把游戏无法返回大厅的问题

《绝地求生》&#xff08;PUBG&#xff09;作为风靡全球的战术竞技大作&#xff0c;凭借其高度还原的战场氛围和扣人心弦的生存挑战吸引了大量游戏玩家。不过&#xff0c;部分玩家在经历了一场紧张激烈的比赛后&#xff0c;遭遇了一个小困扰&#xff1a;游戏未能顺畅过渡到结算…

即时通讯技术文集(第37期):IM代码入门实践(Part1) [共16篇]

为了更好地分类阅读 52im.net 总计1000多篇精编文章&#xff0c;我将在每周三推送新的一期技术文集&#xff0c;本次是第37 期。 [- 1 -] 一种Android端IM智能心跳算法的设计与实现探讨&#xff08;含样例代码&#xff09; [链接] http://www.52im.net/thread-783-1-1.html […

C++基础中的存储类别

存储的类别是变量的属性之一&#xff0c;C语言定义了4种变量的存储类别&#xff0c;分别是auto变量、static变量、register变量和extern变量。以下重点介绍这几种类型。 一、auto变量 auto变量是C默认的存储类型。函数内未加存储类型说明的变量均被称为自动变量&#xff0c;即…

docker-compose完成mysql8.0+环境搭建

1、准备my.cnf文件到指定目录&#xff08;和基础的增加了一个default_authentication_pluginmysql_native_password 的身份验证插件配置信息&#xff09; 原因&#xff1a;官方提到&#xff1a; 该方式可以解决&#xff1a;Authentication plugin ‘caching_ sha2_password‘ c…

FebHost:什么是乌兹别克斯坦.UZ域名?

.uz域名是专门分配给乌兹别克斯坦的国家代码顶级域&#xff08;ccTLD&#xff09;。与代表英国的 “.uk” 或代表法国的 “.fr” 等其他国家代码顶级域类似&#xff0c;”.uz” 是一个代表特定国家的双字母代码。在这种情况下&#xff0c;它代表乌兹别克斯坦。 .uz 域名在建立…

可微分矢量图形光栅化用于编辑和学习

图1. 我们引入了一种通过反向传播将光栅和矢量域联系起来的矢量图形可微分光栅化器。可微分光栅化实现了许多新颖的矢量图形应用。&#xff08;a&#xff09;在几何约束下&#xff0c;通过局部优化图像空间度量&#xff08;如不透明度&#xff09;来实现交互式编辑。&#xff0…

《第一行代码》第二版学习笔记(10)——基于位置的服务

文章目录 一、使用百度定位二、获取经纬度使用百度地图移动到我的位置并让“我”显示在地图上 Android Studio中没有signingReport文件&#xff0c;解决参考文档 一、使用百度定位 下载百度LBS开放平台的SDK 在项目的app.gradle文件下添加依赖&#xff1a;implementation fil…

fb设备驱动框架分析

一、字符设备注册过程&#xff1a; 归根到底&#xff0c;fb设备也是一个字符设备&#xff0c;所以逃不开常规的字符设备驱动框架&#xff1a; Linux内核中编写字符设备驱动通常遵循以下步骤&#xff1a; ①、定义主设备号&#xff1a; 在Linux中&#xff0c;每个字符设备都…

2024洗地机选购指南 | 怎么选洗地机不会被坑?

家里的地板总是需要打扫&#xff0c;但工作忙碌的我们往往没有足够的时间来打理。洗地机不仅能够帮助我们节省宝贵的时间&#xff0c;还能让我们的家变得一尘不染。今天&#xff0c;笔者将为大家讲讲挑选洗地机的技巧&#xff0c;告诉大家怎么挑选洗地机不会被坑&#xff0c;顺…

ECO 视频分类模型

ECO分类模型 ECO 分类模型&#xff0c;可以对视频进行分类&#xff0c;视频是静止画面的集合&#xff0c;并短时间内进行播放&#xff0c;在人眼中形成了视频&#xff0c;通过 FPS 单位进行计算&#xff0c;指的是每秒显示多少张图片。如果直接把图片组合一张大图&#xff0c;…

开源直播电商系统(仿抖音电商模式)

当下&#xff0c;传统的图文电商模式正在走向没落&#xff0c;以“抖音”为首的直播电商模式备受用户追捧&#xff0c;它具有直观与互动的特点&#xff0c;拥有传统电商所不具备的优势。而且&#xff0c;当前正是直播电商的红利期&#xff0c;很多主播和品牌商都通过直播电商业…