proguard.analysis.DominatorCalculator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-core Show documentation
Show all versions of proguard-core Show documentation
ProGuardCORE is a free library to read, analyze, modify, and write Java class files.
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2021 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package proguard.analysis;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import proguard.classfile.Clazz;
import proguard.classfile.Method;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.attribute.CodeAttribute;
import proguard.classfile.attribute.visitor.AttributeVisitor;
import proguard.classfile.instruction.BranchInstruction;
import proguard.classfile.instruction.Instruction;
import proguard.classfile.instruction.InstructionFactory;
import proguard.classfile.instruction.SwitchInstruction;
/**
* Calculate the dominator tree of any method, making
* it possible to determine which instructions are
* guaranteed to be executed before others.
*
*
* This is useful for applications like the {@link CallResolver}
* that would like to know whether an instruction,
* e.g. a method call, is always guaranteed to be executed
* assuming the containing method is invoked, or if
* its execution requires specific branches in the
* method to be taken.
*
*
*
* In principle, dominator analysis is based on a simple equation:
*
* - The entry node dominates only itself
* -
* Any other node's dominator set is calculated by
* forming the intersection of the dominator sets of
* all its control flow predecessors. Afterwards, the node
* itself is also added to its dominator set.
*
*
* Like this, the dominator information is propagated through the
* control flow graph one by one, potentially requiring several
* iterations until the solution is stable, as the dominator sets
* of some predecessors might still be uninitialized when used.
*
*
*
* The implementation here is based on an algorithm
* that solves the underlying dataflow equation using optimized
* {@link BitSet} objects instead of normal sets.
*
*
* @author Samuel Hopstock
*/
public class DominatorCalculator
implements AttributeVisitor
{
/**
* Virtual instruction offset modelling the method
* exit, i.e. all return instructions.
*/
public static final int EXIT_NODE_OFFSET = -1;
/**
* Virtual instruction offset modelling the method
* entry. This is needed such that the method entry
* is guaranteed to have no incoming control flow edges,
* as this would prevent the algorithm from converging
* properly without complicated alternative measures.
*/
public static final int ENTRY_NODE_OFFSET = -2;
private static final List UNCONDITIONAL_BRANCHES = Arrays.asList(Instruction.OP_GOTO,
Instruction.OP_GOTO_W,
Instruction.OP_JSR,
Instruction.OP_JSR_W);
private static final List RETURN_INSTRUCTIONS = Arrays.asList(Instruction.OP_RETURN,
Instruction.OP_IRETURN,
Instruction.OP_LRETURN,
Instruction.OP_FRETURN,
Instruction.OP_DRETURN,
Instruction.OP_ARETURN);
/**
* Maps an offset to its dominator set, represented by
* a {@link BitSet}: This data structure contains at most one
* bit per offset in the method, which is set to 1 if the offset
* dominates the current instruction or 0 otherwise.
*/
private final Map dominatorMap = new HashMap<>();
private int bitSetSize = 0;
/**
* Check if one instruction dominates another one.
* If this is the case, the dominating instruction
* is guaranteed to be executed before the inferior
* instruction. Should you wish to check whether an
* instruction is guaranteed to be executed once the
* containing method is invoked, you can use the
* virtual inferior {@link #EXIT_NODE_OFFSET} as a
* collection for all return instructions.
*
* @param dominator The potentially dominating instruction's
* offset
* @param inferior The potentially dominated instruction's
* offset
* @return true if the potential dominator is indeed
* guaranteed to be executed before the inferior
*/
public boolean dominates(int dominator, int inferior)
{
BitSet dominators = dominatorMap.get(inferior);
if (dominators == null)
{
throw new IllegalStateException("No dominator information known for offset " + inferior);
}
return dominators.get(offsetToIndex(dominator));
}
/**
* As we introduced {@link #ENTRY_NODE_OFFSET} and
* {@link #EXIT_NODE_OFFSET} as virtual instruction
* offsets, the rest of the instructions are shifted
* by those two places. In order to transparently handle
* this, this method converts actual method offsets
* to their internal index counterparts.
*/
private int offsetToIndex(int offset)
{
return offset + 2;
}
// Implementations for AttributeVisitor
@Override
public void visitAnyAttribute(Clazz clazz, Attribute attribute)
{
// Not interested in other attributes
}
@Override
public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
{
dominatorMap.clear();
bitSetSize = offsetToIndex(codeAttribute.u4codeLength);
// Entry node only dominates itself,
// all other nodes are initialized to have all nodes dominate them
// (this is necessary due to the fact that the propagation uses
// intersection, requiring the full universe for uninitialized nodes)
BitSet entryDominators = new BitSet(bitSetSize);
entryDominators.set(offsetToIndex(ENTRY_NODE_OFFSET));
dominatorMap.put(ENTRY_NODE_OFFSET, entryDominators);
dominatorMap.put(EXIT_NODE_OFFSET, initBitSet());
propagateToSuccessor(ENTRY_NODE_OFFSET, 0);
run(codeAttribute);
}
/**
* Iterate through the whole method code one time, in the
* order of a breadth-first search algorithm over the control
* flow graph. I.e. handle direct child nodes before grand
* children.
*/
private void run(CodeAttribute codeAttribute)
{
LinkedHashSet workList = new LinkedHashSet<>();
workList.add(0);
while (!workList.isEmpty())
{
int offset = workList.stream()
.skip(workList.size() - 1)
.findFirst()
.orElseThrow(() -> new IllegalStateException("Can't get last element in non-empty work list"));
workList.remove(offset);
Instruction instruction = InstructionFactory.create(codeAttribute.code, offset);
int instructionLength = instruction.length(offset);
int nextOffset = offset + instructionLength;
boolean nextOffsetExists = nextOffset < codeAttribute.u4codeLength;
Set successors = new HashSet<>();
if (instruction instanceof BranchInstruction)
{
BranchInstruction branch = (BranchInstruction) instruction;
successors.add(offset + branch.branchOffset);
if (nextOffsetExists && !UNCONDITIONAL_BRANCHES.contains(branch.opcode))
{
successors.add(nextOffset);
}
}
else if (instruction instanceof SwitchInstruction)
{
SwitchInstruction switchInstruction = (SwitchInstruction) instruction;
successors.add(offset + switchInstruction.defaultOffset);
for (int jumpOffset : switchInstruction.jumpOffsets)
{
successors.add(offset + jumpOffset);
}
}
else if (RETURN_INSTRUCTIONS.contains(instruction.opcode))
{
propagateToSuccessor(offset, EXIT_NODE_OFFSET);
}
else if (nextOffsetExists)
{
successors.add(nextOffset);
}
for (int successor : successors)
{
if (propagateToSuccessor(offset, successor))
{
workList.add(successor);
}
}
}
}
/**
* Create a {@link BitSet} where all bits representing
* offsets in the method are set to true.
*/
private BitSet initBitSet()
{
BitSet result = new BitSet(bitSetSize);
result.set(0, bitSetSize);
return result;
}
/**
* Propagate dominator information to a successor of the
* current instruction.
*
* @return true if this propagation step changed the
* successor's dominator information
*/
private boolean propagateToSuccessor(int curr, int successor)
{
BitSet currDominators = dominatorMap.computeIfAbsent(curr, o -> initBitSet());
BitSet successorDominators = dominatorMap.computeIfAbsent(successor, o -> initBitSet());
BitSet beforePropagation = (BitSet) successorDominators.clone();
successorDominators.and(currDominators);
successorDominators.set(offsetToIndex(successor));
return !beforePropagation.equals(successorDominators);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy