【静态分析】软件分析课程实验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 中你需要了解的类

为了实现死代码检测算法,你需要知道 CFGIR,还有其他与活跃变量分析、常量传播分析结果有关的类(比如 CPFactDataflowResult 等),不过你已经在之前的作业中使用过了它们,应该对它们很熟悉了!接下来我们介绍更多本次作业中将会用到的和 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_TRUEIF_FALSESWITCH_CASESWITCH_DEFAULTIF_TRUEIF_FALSE 表示从 if 语句到它的两个分支的出边,就像下面的例子所示:

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

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

pascal.taie.ir.stmt.IfStmt 的子类)

这个类表示程序中的 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;

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

  • pascal.taie.ir.stmt.SwitchStmtStmt 的子类)

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

  • pascal.taie.ir.stmt.AssignStmtStmt 的子类)

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

  • 事实上,AssignStmtDefinitionStmt 两个子类的其中一个(另一个是 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.javaConstantPropagation.java。你可以拷贝你之前作业中的实现。另外,你也需要完成一个同时支持前向分析和后向分析的 worklist 求解器。你可以从作业 2 中拷贝你之前对 Solver.javaWorkListSolver.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() 方法包含获取 IRCFG 的代码。
  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 的结果转化并赋值给一个新的变量。这种语法可以简化类型判断和类型转换的代码。注意,被转换的对象必须是finaleffectively 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://xiahunao.cn/news/3032396.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins流水线部署Maven项目

使用Jenkins的流水线功能&#xff0c;构建部署Java Maven项目&#xff0c;步骤很简单但是不少细节需要注意。 一、安装 Jenkins的安装步骤和流程就不具体描述&#xff0c;这里主要介绍一下安装时要注意的几个问题。 1、Jenkins尽量安装最新的几个版本&#xff0c;否则安装完成…

6个超TM好用的神仙App推荐!

1. AI文本视频生成工具——Jurilu Jurilu 是一款功能强大的 AI 文本视频生成器&#xff0c;允许用户快速将文本内容转换成极具吸引力的视频。它的使用非常简单&#xff1a;只需要输入文字&#xff0c;选择想要的样式和模板&#xff0c;Jurilu 就会自动将文字转换成生动的视频。…

【C语言】/*操作符(下)*/

目录 一、操作符的分类 二、二进制和进制转换 2.1 进制 2.2 进制之间的转换 三、原码、反码、补码 四、单目操作符 五、逗号表达式 六、下标引用操作符[] 七、函数调用操作符() 八、结构体成员访问操作符 8.1 直接访问操作符(.) 8.2 间接访问操作符(->) 九、操作符…

干货教程【AI篇】| 目前全球最强AI换脸工具swapface详细图文教程及整合包下载

需要这个工具整合包的小伙伴可以关注一下文章底部公众号&#xff0c;回复关键词【swapface】即可获取。 从我们的链接下载&#xff0c;得到这个exe文件 双击运行即可进入安装界面 如下图所示已经在安装中啦 安装好之后我们根据上面的安装路径找到要执行的文件 双击红框中的…

Doris【部署 01】Linux部署MPP数据库Doris稳定版(下载+安装+连接+测试)

本次安装测试的为稳定版2.0.8官方文档 https://doris.apache.org/zh-CN/docs/2.0/get-starting/quick-start 这个简短的指南将告诉你如何下载 Doris 最新稳定版本&#xff0c;在单节点上安装并运行它&#xff0c;包括创建数据库、数据表、导入数据及查询等。 Linux部署稳定版Do…

Excel-VBA报错01-解决方法

【已删除的部件:部件/xl/vbaProject.bin。(Visual Basic for Applications(VBA))】 1.问题复现&#xff1a; Win10 &#xff1b;64位 &#xff1b;Office Excel 2016 打开带有宏的Excel文件&#xff0c;报错&#xff1a;【已删除的部件&#xff1a;部件/xl/vbaProject.bin。…

MFC DLL注入失败一些错误总结

使用cheat Engine为MFC窗口程序注入DLL时一定要注意&#xff0c;被注入的exe程序和注入的DLL 的绝对路径中一定不要带有中文字符&#xff0c;否则会遇到各种各样的奇怪错误&#xff0c;如下所示&#xff1a; 以下是dll绝对路径中均含有中文字符&#xff0c;会报错误&#xff…

一种简单的小报表本地缓存方案

适应如下场景&#xff1a;关联表多&#xff0c;接口响应慢&#xff0c;报表数据不多&#xff0c;可能就十多行。参数也固定&#xff0c;实时性要求不高&#xff0c;隔那么半小时刷新一次&#xff0c;查询性能要求高&#xff0c;给领导看的&#xff0c;要求很快。 使用示例&…

【桌面应用开发】Rust+Tauri框架项目打包操作

1.项目npm install下载项目依赖&#xff08;需要配置好node.js环境&#xff09; 可参考&#xff1a;https://blog.csdn.net/m0_64346565/article/details/138319651 2.自定义图标&#xff08;项目初始化开始第一次需要配置生成&#xff0c;后面可跳过这一步骤&#xff09; Ta…

ITIL4视角下的IT监控与故障管理:守护服务健康的双刃剑

引言&#xff1a;监控的曙光 在IT服务管理的浩瀚星图中&#xff0c;"监控"这一璀璨星辰终于得到了应有的重视与聚焦。ITIL4的出台&#xff0c;不仅明确将监控告警纳入事件管理的广阔宇宙&#xff0c;而且强调了其在预防故障、保障服务连续性中的核心地位。当组织拥抱…

k8s设置在任意node里执行kubectl 命令

一、问题 正常来讲kubectl 只能在master node 里运行 当我们尝试在某个 node 节点来执行时&#xff0c; 通常会遇到下面错误 执行错误&#xff1a;The connection to the server localhost:8080 was refused - did you specify the 原因&#xff1a;因为k8s的各个组建&#xf…

Oracle 多表查询

关联查询 一、sql:1992语法的连接笛卡尔积等值连接非等值连接自连接外连接 二、sql:1999语法的连接交叉连接自然连接USING创建连接ON创建连接左外连接右外连接FULL OUTER JOININNER JOIN 三、子查询子查询的种类单行子查询多行子查询 在From字句中使用子查询练习 四、行转列 一…

小兴教你做平衡小车-平衡车主板绘制(V4版本 b站课程所使用版本)

文章目录 1 原理图总览2 原理图各模块细致图2.1 2.54mm插针(stm32最小系统扩展接口)2.2 OLED显示2.3 MPU60502.4 TB6612驱动电路2.5 2.54mm排座(stm32最小系统连接接口)2.6 测距模块2.7 蓝牙模块2.8 蜂鸣器模块2.9 电池电压检测电路2.10 M3固定孔2.11 用户小灯模块2.12 电源接口…

在go-zero中使用jwt

gozero使用jwt 两个步骤 获取token验证token 前端获取token 先编写 jwt.api 文件&#xff0c;放在api目录下 syntax "v1"info (title: "type title here"desc: "type desc here"author: "type author here"email: &quo…

无限集中的最小数字

题目链接 无限集中的最小数字 题目描述 注意点 1 < num < 1000 解答思路 由题意得&#xff0c;可以理解为最初集合中有1~1000之间的所有数字&#xff0c;如果集合中存在数字&#xff0c;则添加时不会有任何操作&#xff1b;在移除集合中的元素时&#xff0c;会按顺序…

记录使用极空间NAS通过Docker部署小皮面板(PhpStydy)运行 八图片当面付支付宝接口 PHP项目的遭遇

事件的起因还得从我用八图片的图片加密支付跳转功能&#xff0c;实现打赏金额发案例源码下载链接挣个烟钱的事。八图片的支付接口是PHP web项目的。正好我有个极空间的NAS&#xff0c;搭建到NAS上省去了买主机的费用。 导读 八图片是什么&#xff1f;极空间NAS 部署 PHP网站安装…

Go实现树莓派读取at24c02 eeprom读写数据

步骤 启用i2c 参考 Go实现树莓派读取bh1750光照强度 代码 package mainimport ("fmt""periph.io/x/conn/v3/i2c" )type AT24C02Device struct {dev *i2c.Dev }func NewAT24C02Device(addr uint16, bus i2c.BusCloser) (*AT24C02Device, error) {var (d…

图像融合-下游任务(目标检测、实例分割、深度估计)

下游任务: 采用目标检测、实例分割和深度估计的下游任务来验证图像融合结果质量。 文章目录 下游任务:1.目标检测2.实例分割3.深度估计Update1.目标检测 YOLOv8:https://github.com/ultralytics/ultralytics 步骤内容第一步下载项目到本地第二步安装README中项目相关的环…

LibreNMS简介

目录 1 LibreNMS简单介绍1.1 LibreNMS介绍 2 安装2.1 Ubuntu安装1、安装依赖2、添加 librenms 用户3、下载 LibreNMS4、设置权限5、安装 PHP 依赖项6、设置时区7、配置 MariaDB8、配置 PHP-FPM9、配置 Web 服务器10、启用 lnms 命令11、配置 snmpd12、cron13、启用调度程序14、…

mysql NDBcluster数据库集群介绍、部署及配置

前言: MySQL集群是一个无共享的、分布式节点架构的存储方案,旨在提供容错性和高性能。它由三个主要节点组成:管理节点(MGM)、数据节点和SQL节点。 管理节点(MGM) 定义与用途:管理节点是MySQL Cluster的控制中心,负责管理集群内的其他节点。它提供配置数据,启动和停止…