官网:
作业 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 中你需要了解的类
为了实现死代码检测算法,你需要知道 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 assignment
a = 1;
b = a * 2; // dead assignment
c = 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 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);
}
}
}
}
}
提示:
- 在这次作业中,Tai-e 会在运行死代码检测之前自动运行活跃变量分析和常量传播分析。我们在
DeadCodeDetection.analyze()
中提供了用来获得这两种分析算法针对目标IR
的分析结果,这样你可以直接使用它们。另外,analyze()
方法包含获取IR
的CFG
的代码。- 正如第 2.2 节提到的那样,某些赋值语句等号右侧的表达式可能含有副作用,因此不能被当作 dead assignments。我们在
DeadCodeDetection
中提供了一个辅助方法hasNoSideEffect(RValue)
,用来帮助你检查一个表达式是否含有副作用。- 在遍历 CFG 时,你需要对当前正在访问的节点使用
CFG.getOutEdgesOf()
来帮助获得之后要被访问的后继节点。这个 API 返回给定节点在 CFG 上的出边,所以你可以用边的信息(在第 3.1 节介绍过)来帮助找出分支不可达代码。- 当在寻找分支不可达代码时,你可以使用
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语句的代码块中使用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 框架(教学版)配置指南 所介绍的那样使用它来测试你的实现。