【静态分析】软件分析课程实验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 code
    foo(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;
    else
        c = 6666; // unreachable branch
    return 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 branch
        case 2: y = 200;
        case 3: y = 300; break; // fall through
        default: y = 666; // unreachable branch
    }
    return y;
}

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

2.2 无用赋值

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

int deadAssign() {
    int a, b, c;
    a = 0; // dead assignment
    a = 1;
    b = a * 2; // dead assignment
    c = 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 assignment
    a = 1;
    b = a * 2; // dead assignment
    c = 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 extends
        AbstractDataflowAnalysis<Stmt, SetFact<Var>> {

    public static final String ID = "livevar";

    public LiveVariableAnalysis(AnalysisConfig config) {
        super(config);
    }

    @Override
    public boolean isForward() {
        return false;
    }

    @Override
    public SetFact<Var> newBoundaryFact(CFG<Stmt> cfg) {
        // TODO - finish me
        return new SetFact<>();
    }

    @Override
    public SetFact<Var> newInitialFact() {
        // TODO - finish me
        return new SetFact<>();
    }

    @Override
    public void meetInto(SetFact<Var> fact, SetFact<Var> target) {
        // TODO - finish me
        target.union(fact);
    }

    @Override
    public boolean transferNode(Stmt stmt, SetFact<Var> in, SetFact<Var> out) {
        // TODO - finish me
        Optional<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 extends
        AbstractDataflowAnalysis<Stmt, CPFact> {

    public static final String ID = "constprop";

    public ConstantPropagation(AnalysisConfig config) {
        super(config);
    }

    @Override
    public boolean isForward() {
        return true;
    }

    @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;
    }

    @Override
    public CPFact newInitialFact() {
        // TODO - finish me
        return new CPFact();
    }

    @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));
        }
    }

    /**
     * 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();
        }
    }

    @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。有更新,返回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 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()) {
                if (v1.isNAC() && v2.isConstant() && exp instanceof ArithmeticExp) {  // x = a / 0,x = a % 0,x 的值将会是 UNDEF
                    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 {  // 二元表达式中的其他类型表达式
                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 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());
        }
    }

    protected void initializeBackward(CFG<Node> cfg, DataflowResult<Node, Fact> result) {
        // TODO - finish me
        // Init Exit node
        result.setInFact(cfg.getExit(), analysis.newBoundaryFact(cfg));

        // Init other nodes
        for (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);
    }

    @Override
    protected 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);
                }
            }
        }
    }

    @Override
    protected 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的OUT
                analysis.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语句的代码块中使用myObj
    myObj.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);
    }

    @Override
    public Set<Stmt> analyze(IR ir) {
        // obtain CFG
        CFG<Stmt> cfg = ir.getResult(CFGBuilder.ID);
        // obtain result of constant propagation
        DataflowResult<Stmt, CPFact> constants =
                ir.getResult(ConstantPropagation.ID);
        // obtain result of live variable analysis
        DataflowResult<Stmt, SetFact<Var>> liveVars =
                ir.getResult(LiveVariableAnalysis.ID);
        // keep statements (dead code) sorted in the resulting set
        Set<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)) {  // dead
                        if (hasNoSideEffect(rValue)) {  // no side effect
                            f = 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) {  // 永远true
                        for (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 {  // 永远false
                        for (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,如果没有,将执行default
                    for (Edge<Stmt> edge : cfg.getOutEdgesOf(switchStmt)) {  // 获取所有出边
                        if (edge.getKind() == Edge.Kind.SWITCH_CASE) {  // 如果是case类型边
                            int caseValue = edge.getCaseValue();  // 获取case值
                            if (caseValue == constant) {   // 如果是匹配的case
                                match = true;
                                if (!reached.contains(edge.getTarget()))
                                    stmts.addLast(edge.getTarget());
                            }
                        }
                    }
                    if (!match) {  // 如果不匹配,执行default
                        Stmt 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 heap
        if (rvalue instanceof NewExp ||
                // cast may trigger ClassCastException
                rvalue instanceof CastExp ||
                // static field access may trigger class initialization
                // instance field access may trigger NPE
                rvalue instanceof FieldAccess ||
                // array access may trigger NPE
                rvalue instanceof ArrayAccess) {
            return false;
        }
        if (rvalue instanceof ArithmeticExp) {
            ArithmeticExp.Op op = ((ArithmeticExp) rvalue).getOperator();
            // may trigger DivideByZeroException
            return 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 框架(教学版)配置指南 所介绍的那样使用它来测试你的实现。

  • 23
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值