Add nullness analysis

This commit is contained in:
Alexey Andreev 2016-12-27 00:28:37 +03:00
parent ae5e1e4962
commit 9dc4b47253
18 changed files with 791 additions and 181 deletions

View File

@ -62,6 +62,7 @@ public class Variable implements VariableReader {
this.debugName = debugName;
}
@Override
public String getLabel() {
return label;
}

View File

@ -22,5 +22,7 @@ public interface VariableReader {
String getDebugName();
String getLabel();
int getRegister();
}

View File

@ -15,17 +15,75 @@
*/
package org.teavm.model.analysis;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashSet;
import java.util.Set;
import org.teavm.model.BasicBlock;
import org.teavm.model.Instruction;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.Phi;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.TryCatchJoint;
import org.teavm.model.Variable;
import org.teavm.model.util.DefinitionExtractor;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.PhiUpdater;
public class NullnessInformation {
NullnessInformation() {
private Program program;
private BitSet synthesizedVariables;
private PhiUpdater phiUpdater;
private BitSet notNullVariables;
private BitSet nullVariables;
NullnessInformation(Program program, BitSet synthesizedVariables, PhiUpdater phiUpdater, BitSet notNullVariables,
BitSet nullVariables) {
this.program = program;
this.synthesizedVariables = synthesizedVariables;
this.phiUpdater = phiUpdater;
this.notNullVariables = notNullVariables;
this.nullVariables = nullVariables;
}
public boolean isNotNull(Variable variable) {
return notNullVariables.get(variable.getIndex());
}
public boolean isNull(Variable variable) {
return nullVariables.get(variable.getIndex());
}
public void dispose() {
Set<Phi> phisToRemove = new HashSet<>(phiUpdater.getSynthesizedPhis());
Set<TryCatchJoint> jointsToRemove = new HashSet<>(phiUpdater.getSynthesizedJoints());
DefinitionExtractor defExtractor = new DefinitionExtractor();
InstructionVariableMapper variableMapper = new InstructionVariableMapper(var -> {
int source = phiUpdater.getSourceVariable(var.getIndex());
return source >= 0 ? program.variableAt(source) : var;
});
for (BasicBlock block : program.getBasicBlocks()) {
block.getPhis().removeIf(phisToRemove::contains);
for (TryCatchBlock tryCatch : block.getTryCatchBlocks()) {
tryCatch.getJoints().removeIf(jointsToRemove::contains);
}
for (Instruction insn : block) {
insn.acceptVisitor(defExtractor);
if (Arrays.stream(defExtractor.getDefinedVariables())
.anyMatch(var -> synthesizedVariables.get(var.getIndex()))) {
insn.delete();
} else {
insn.acceptVisitor(variableMapper);
}
}
}
}
public static NullnessInformation build(Program program) {
NullnessInformation instance = new NullnessInformation();
return instance;
public static NullnessInformation build(Program program, MethodDescriptor methodDescriptor) {
NullnessInformationBuilder builder = new NullnessInformationBuilder(program, methodDescriptor);
builder.build();
return new NullnessInformation(program, builder.synthesizedVariables, builder.phiUpdater,
builder.notNullVariables, builder.nullVariables);
}
}

View File

@ -15,11 +15,18 @@
*/
package org.teavm.model.analysis;
import com.carrotsearch.hppc.IntArrayDeque;
import com.carrotsearch.hppc.IntDeque;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import com.carrotsearch.hppc.IntOpenHashSet;
import com.carrotsearch.hppc.IntSet;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.common.GraphUtils;
import org.teavm.model.BasicBlock;
import org.teavm.model.Incoming;
import org.teavm.model.Instruction;
@ -32,7 +39,7 @@ import org.teavm.model.Variable;
import org.teavm.model.instructions.AbstractInstructionVisitor;
import org.teavm.model.instructions.ArrayLengthInstruction;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.BranchingCondition;
import org.teavm.model.instructions.BinaryBranchingInstruction;
import org.teavm.model.instructions.BranchingInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.CloneArrayInstruction;
@ -40,7 +47,6 @@ import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ConstructMultiArrayInstruction;
import org.teavm.model.instructions.GetFieldInstruction;
import org.teavm.model.instructions.InstructionVisitor;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.MonitorEnterInstruction;
import org.teavm.model.instructions.MonitorExitInstruction;
@ -49,20 +55,22 @@ import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
import org.teavm.model.instructions.UnwrapArrayInstruction;
import org.teavm.model.util.DominatorWalker;
import org.teavm.model.util.DominatorWalkerCallback;
import org.teavm.model.util.PhiUpdater;
class NullnessInformationBuilder {
private Program program;
private MethodDescriptor methodDescriptor;
private BitSet notNullVariables = new BitSet();
private BitSet nullVariables = new BitSet();
private BitSet synthesizedVariables = new BitSet();
private PhiUpdater phiUpdater;
BitSet notNullVariables = new BitSet();
BitSet nullVariables = new BitSet();
BitSet synthesizedVariables = new BitSet();
PhiUpdater phiUpdater;
private List<NullConstantInstruction> nullInstructions = new ArrayList<>();
private List<NullCheckInstruction> notNullInstructions = new ArrayList<>();
private Graph assignmentGraph;
private int[] nullPredecessorsLeft;
private int[] notNullPredecessorsLeft;
private int[] sccIndexes;
NullnessInformationBuilder(Program program, MethodDescriptor methodDescriptor) {
this.program = program;
@ -72,12 +80,13 @@ class NullnessInformationBuilder {
void build() {
extendProgram();
buildAssignmentGraph();
findKnownNullness();
propagateNullness();
}
private void extendProgram() {
insertAdditionalVariables();
notNullVariables.set(0);
Variable[] parameters = new Variable[methodDescriptor.parameterCount() + 1];
for (int i = 0; i < parameters.length; ++i) {
parameters[i] = program.variableAt(i);
@ -89,55 +98,28 @@ class NullnessInformationBuilder {
}
private void insertAdditionalVariables() {
DominatorWalker walker = new DominatorWalker(program);
NullExtensionVisitor ev = new NullExtensionVisitor();
for (BasicBlock block : program.getBasicBlocks()) {
Instruction lastInstruction = block.getLastInstruction();
if (lastInstruction instanceof BranchingInstruction) {
BranchingInstruction branching = (BranchingInstruction) lastInstruction;
if (branching.getCondition() == BranchingCondition.NULL) {
insertNullAndNotNull(branching.getOperand(), branching.getConsequent(), branching.getAlternative());
} else if (branching.getCondition() == BranchingCondition.NOT_NULL) {
insertNullAndNotNull(branching.getOperand(), branching.getAlternative(), branching.getConsequent());
}
}
for (Instruction instruction = block.getFirstInstruction(); instruction != null;) {
Instruction next = instruction.getNext();
instruction.acceptVisitor(ev);
instruction = next;
}
}
walker.walk(ev);
}
private void collectAdditionalVariables() {
for (NullConstantInstruction nullInstruction : nullInstructions) {
nullVariables.set(nullInstruction.getReceiver().getIndex());
synthesizedVariables.set(nullInstruction.getReceiver().getIndex());
}
for (NullCheckInstruction notNullInstruction : notNullInstructions) {
notNullVariables.set(notNullInstruction.getReceiver().getIndex());
synthesizedVariables.set(notNullInstruction.getReceiver().getIndex());
}
synthesizedVariables.or(nullVariables);
synthesizedVariables.or(notNullVariables);
nullInstructions.clear();
notNullInstructions.clear();
}
private void insertNullAndNotNull(Variable variable, BasicBlock nullBlock, BasicBlock notNullBlock) {
NullCheckInstruction notNullInstruction = new NullCheckInstruction();
notNullInstruction.setValue(variable);
notNullInstruction.setReceiver(variable);
notNullBlock.addFirst(notNullInstruction);
notNullInstructions.add(notNullInstruction);
NullConstantInstruction nullInstruction = new NullConstantInstruction();
nullInstruction.setReceiver(variable);
nullBlock.addFirst(nullInstruction);
nullInstructions.add(nullInstruction);
}
private void buildAssignmentGraph() {
GraphBuilder builder = new GraphBuilder();
GraphBuilder builder = new GraphBuilder(program.variableCount());
for (BasicBlock block : program.getBasicBlocks()) {
for (Phi phi : block.getPhis()) {
for (Incoming incoming : phi.getIncomings()) {
@ -161,25 +143,93 @@ class NullnessInformationBuilder {
}
assignmentGraph = builder.build();
// TODO: handle SCCs
sccIndexes = new int[program.variableCount()];
if (assignmentGraph.size() > 0) {
int[][] sccs = GraphUtils.findStronglyConnectedComponents(assignmentGraph, new int[]{0});
for (int i = 0; i < sccs.length; ++i) {
for (int sccNode : sccs[i]) {
sccIndexes[sccNode] = i + 1;
}
}
}
nullPredecessorsLeft = new int[assignmentGraph.size()];
notNullPredecessorsLeft = new int[assignmentGraph.size()];
for (int i = 0; i < assignmentGraph.size(); ++i) {
nullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
notNullPredecessorsLeft[i] = assignmentGraph.incomingEdgesCount(i);
}
}
private void findKnownNullness() {
for (BasicBlock block : program.getBasicBlocks()) {
for (Instruction instruction : block) {
instruction.acceptVisitor(nullnessVisitor);
if (sccIndexes[i] > 0) {
for (int predecessor : assignmentGraph.outgoingEdges(i)) {
if (sccIndexes[predecessor] == sccIndexes[i]) {
notNullPredecessorsLeft[i]--;
}
}
}
}
}
class NullExtensionVisitor extends AbstractInstructionVisitor {
private void propagateNullness() {
if (assignmentGraph.size() == 0) {
return;
}
IntDeque deque = new IntArrayDeque();
for (int i = notNullVariables.nextSetBit(0); i >= 0; i = notNullVariables.nextSetBit(i + 1)) {
deque.addLast(i);
}
boolean[] visited = new boolean[program.variableCount()];
while (!deque.isEmpty()) {
int node = deque.removeFirst();
if (visited[node]) {
continue;
}
visited[node] = true;
for (int successor : assignmentGraph.outgoingEdges(node)) {
if (sccIndexes[successor] == 0 || sccIndexes[successor] != sccIndexes[node]) {
if (--notNullPredecessorsLeft[successor] == 0) {
deque.addLast(successor);
}
}
}
}
}
class NullExtensionVisitor extends AbstractInstructionVisitor implements DominatorWalkerCallback<State> {
State currentState;
BasicBlock currentBlock;
IntIntMap nullSuccessors = new IntIntOpenHashMap();
IntIntMap notNullSuccessors = new IntIntOpenHashMap();
@Override
public State visit(BasicBlock block) {
currentState = new State();
currentBlock = block;
if (nullSuccessors.containsKey(block.getIndex())) {
int varIndex = nullSuccessors.remove(block.getIndex());
insertNullInstruction(program.variableAt(varIndex));
}
if (notNullSuccessors.containsKey(block.getIndex())) {
int varIndex = notNullSuccessors.remove(block.getIndex());
insertNotNullInstruction(null, program.variableAt(varIndex));
}
for (Instruction insn : block) {
insn.acceptVisitor(this);
}
return currentState;
}
@Override
public void endVisit(BasicBlock block, State state) {
for (int rollbackToNull : state.newlyNonNull.toArray()) {
notNullVariables.clear(rollbackToNull);
}
for (int rollbackToNotNull : state.newlyNull.toArray()) {
nullVariables.clear(rollbackToNotNull);
}
}
@Override
public void visit(GetFieldInstruction insn) {
if (insn.getInstance() != null) {
@ -226,16 +276,11 @@ class NullnessInformationBuilder {
insertNotNullInstruction(insn, insn.getObjectRef());
}
private void insertNotNullInstruction(Instruction currentInstruction, Variable var) {
NullCheckInstruction insn = new NullCheckInstruction();
insn.setReceiver(var);
insn.setValue(var);
notNullInstructions.add(insn);
currentInstruction.insertNext(insn);
@Override
public void visit(StringConstantInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex());
}
}
private InstructionVisitor nullnessVisitor = new AbstractInstructionVisitor() {
@Override
public void visit(ClassConstantInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex());
@ -247,8 +292,9 @@ class NullnessInformationBuilder {
}
@Override
public void visit(StringConstantInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex());
public void visit(AssignInstruction insn) {
notNullVariables.set(insn.getReceiver().getIndex(), notNullVariables.get(insn.getAssignee().getIndex()));
nullVariables.set(insn.getReceiver().getIndex(), nullVariables.get(insn.getAssignee().getIndex()));
}
@Override
@ -268,7 +314,95 @@ class NullnessInformationBuilder {
@Override
public void visit(NullCheckInstruction insn) {
super.visit(insn);
notNullVariables.set(insn.getReceiver().getIndex());
}
};
@Override
public void visit(BranchingInstruction insn) {
switch (insn.getCondition()) {
case NOT_NULL:
notNullSuccessors.put(insn.getConsequent().getIndex(), insn.getOperand().getIndex());
nullSuccessors.put(insn.getAlternative().getIndex(), insn.getOperand().getIndex());
break;
case NULL:
nullSuccessors.put(insn.getConsequent().getIndex(), insn.getOperand().getIndex());
notNullSuccessors.put(insn.getAlternative().getIndex(), insn.getOperand().getIndex());
break;
default:
break;
}
}
@Override
public void visit(BinaryBranchingInstruction insn) {
Variable first = insn.getFirstOperand();
Variable second = insn.getSecondOperand();
if (nullVariables.get(first.getIndex())) {
first = second;
} else if (!nullVariables.get(second.getIndex())) {
return;
}
switch (insn.getCondition()) {
case REFERENCE_EQUAL:
notNullSuccessors.put(insn.getConsequent().getIndex(), first.getIndex());
nullSuccessors.put(insn.getAlternative().getIndex(), first.getIndex());
break;
case REFERENCE_NOT_EQUAL:
nullSuccessors.put(insn.getConsequent().getIndex(), first.getIndex());
notNullSuccessors.put(insn.getAlternative().getIndex(), first.getIndex());
break;
default:
break;
}
}
private void insertNotNullInstruction(Instruction currentInstruction, Variable var) {
if (notNullVariables.get(var.getIndex())) {
return;
}
NullCheckInstruction insn = new NullCheckInstruction();
insn.setReceiver(var);
insn.setValue(var);
notNullInstructions.add(insn);
if (currentInstruction != null) {
currentInstruction.insertNext(insn);
} else {
currentBlock.addFirst(insn);
}
markAsNonNull(var);
}
private void insertNullInstruction(Variable var) {
if (nullVariables.get(var.getIndex())) {
return;
}
NullConstantInstruction insn = new NullConstantInstruction();
insn.setReceiver(var);
nullInstructions.add(insn);
currentBlock.addFirst(insn);
markAsNull(var);
}
private void markAsNonNull(Variable var) {
if (notNullVariables.get(var.getIndex())) {
return;
}
notNullVariables.set(var.getIndex());
currentState.newlyNonNull.add(var.getIndex());
}
private void markAsNull(Variable var) {
if (nullVariables.get(var.getIndex())) {
return;
}
nullVariables.set(var.getIndex());
currentState.newlyNull.add(var.getIndex());
}
}
static class State {
IntSet newlyNonNull = new IntOpenHashSet();
IntSet newlyNull = new IntOpenHashSet();
}
}

View File

@ -16,8 +16,10 @@
package org.teavm.model.text;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import org.teavm.model.*;
import org.teavm.model.instructions.*;
@ -25,9 +27,29 @@ import org.teavm.model.instructions.*;
class InstructionStringifier implements InstructionReader {
private TextLocation location;
private StringBuilder sb;
private String[] variableLabels;
InstructionStringifier(StringBuilder sb) {
InstructionStringifier(StringBuilder sb, ProgramReader program) {
this.sb = sb;
variableLabels = new String[program.variableCount()];
Set<String> occupiedLabels = new HashSet<>();
for (int i = 0; i < program.variableCount(); ++i) {
VariableReader var = program.variableAt(i);
String suggestedName = var.getLabel() != null ? var.getLabel() : Integer.toString(i);
if (!occupiedLabels.add(suggestedName)) {
int suffix = 1;
String base = suggestedName + "_";
do {
suggestedName = base + suffix++;
} while (!occupiedLabels.add(suggestedName));
}
variableLabels[i] = suggestedName;
}
}
public String getVariableLabel(int index) {
return variableLabels[index];
}
public TextLocation getLocation() {
@ -44,40 +66,58 @@ class InstructionStringifier implements InstructionReader {
sb.append("nop");
}
InstructionStringifier append(String str) {
sb.append(str);
return this;
}
InstructionStringifier append(int value) {
sb.append(value);
return this;
}
InstructionStringifier append(char value) {
sb.append(value);
return this;
}
InstructionStringifier appendLocalVar(VariableReader var) {
return append("@").append(variableLabels[var.getIndex()]);
}
@Override
public void classConstant(VariableReader receiver, ValueType cst) {
sb.append("@").append(receiver.getIndex()).append(" := classOf ");
escapeIdentifierIfNeeded(cst.toString(), sb);
appendLocalVar(receiver).append(" := classOf ").escapeIdentifierIfNeeded(cst.toString());
}
@Override
public void nullConstant(VariableReader receiver) {
sb.append("@").append(receiver.getIndex()).append(" := null");
appendLocalVar(receiver).append(" := null");
}
@Override
public void integerConstant(VariableReader receiver, int cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst);
appendLocalVar(receiver).append(" := " + cst);
}
@Override
public void longConstant(VariableReader receiver, long cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append("L");
appendLocalVar(receiver).append(" := " + cst + "L");
}
@Override
public void floatConstant(VariableReader receiver, float cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst).append('F');
appendLocalVar(receiver).append(" := " + cst + 'F');
}
@Override
public void doubleConstant(VariableReader receiver, double cst) {
sb.append("@").append(receiver.getIndex()).append(" := ").append(cst);
appendLocalVar(receiver).append(" := " + cst);
}
@Override
public void stringConstant(VariableReader receiver, String cst) {
sb.append("@").append(receiver.getIndex()).append(" := '");
appendLocalVar(receiver).append(" := '");
escapeStringLiteral(cst, sb);
sb.append("'");
}
@ -114,7 +154,7 @@ class InstructionStringifier implements InstructionReader {
}
}
private static void escapeIdentifierIfNeeded(String s, StringBuilder sb) {
private InstructionStringifier escapeIdentifierIfNeeded(String s) {
boolean needsEscaping = false;
if (s.isEmpty()) {
needsEscaping = true;
@ -133,87 +173,88 @@ class InstructionStringifier implements InstructionReader {
} else {
sb.append(s);
}
return this;
}
@Override
public void binary(BinaryOperation op, VariableReader receiver, VariableReader first, VariableReader second,
NumericOperandType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(first.getIndex()).append(" ");
appendLocalVar(receiver).append(" := ").appendLocalVar(first).append(" ");
switch (op) {
case ADD:
sb.append("+");
append("+");
break;
case AND:
sb.append("&");
append("&");
break;
case COMPARE:
sb.append("compareTo");
append("compareTo");
break;
case DIVIDE:
sb.append("/");
append("/");
break;
case MODULO:
sb.append("%");
append("%");
break;
case MULTIPLY:
sb.append("*");
append("*");
break;
case OR:
sb.append("|");
append("|");
break;
case SHIFT_LEFT:
sb.append("<<");
append("<<");
break;
case SHIFT_RIGHT:
sb.append(">>");
append(">>");
break;
case SHIFT_RIGHT_UNSIGNED:
sb.append(">>>");
append(">>>");
break;
case SUBTRACT:
sb.append("-");
append("-");
break;
case XOR:
sb.append("^");
append("^");
break;
}
sb.append(" @").append(second.getIndex());
append(" ").appendLocalVar(second);
}
@Override
public void negate(VariableReader receiver, VariableReader operand, NumericOperandType type) {
sb.append("@").append(receiver.getIndex()).append(" := -").append(" @").append(operand.getIndex());
appendLocalVar(receiver).append(" := -").append(" ").appendLocalVar(operand);
}
@Override
public void assign(VariableReader receiver, VariableReader assignee) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(assignee.getIndex());
appendLocalVar(receiver).append(" := ").appendLocalVar(assignee);
}
@Override
public void cast(VariableReader receiver, VariableReader value, ValueType targetType) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex())
.append(" to ");
escapeIdentifierIfNeeded(targetType.toString(), sb);
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value).append(" to ")
.escapeIdentifierIfNeeded(targetType.toString());
}
@Override
public void cast(VariableReader receiver, VariableReader value, NumericOperandType sourceType,
NumericOperandType targetType) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex())
.append(" from ").append(sourceType).append(" to ").append(targetType);
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value)
.append(" from ").append(sourceType.toString()).append(" to ").append(targetType.toString());
}
@Override
public void cast(VariableReader receiver, VariableReader value, IntegerSubtype type,
CastIntegerDirection direction) {
sb.append("@").append(receiver.getIndex()).append(" := cast @").append(value.getIndex());
appendLocalVar(receiver).append(" := cast ").appendLocalVar(value);
switch (direction) {
case FROM_INTEGER:
sb.append(" from int to ").append(type.name().toLowerCase(Locale.ROOT));
append(" from int to ").append(type.name().toLowerCase(Locale.ROOT));
break;
case TO_INTEGER:
sb.append(" from ").append(type.name().toLowerCase(Locale.ROOT)).append(" to int");
append(" from ").append(type.name().toLowerCase(Locale.ROOT)).append(" to int");
break;
}
}
@ -221,7 +262,7 @@ class InstructionStringifier implements InstructionReader {
@Override
public void jumpIf(BranchingCondition cond, VariableReader operand, BasicBlockReader consequent,
BasicBlockReader alternative) {
sb.append("if @").append(operand.getIndex()).append(" ");
append("if ").appendLocalVar(operand).append(" ");
switch (cond) {
case EQUAL:
sb.append("== 0");
@ -248,174 +289,164 @@ class InstructionStringifier implements InstructionReader {
sb.append("=== null");
break;
}
sb.append(" then goto $").append(consequent.getIndex()).append(" else goto $").append(alternative.getIndex());
append(" then goto $").append(consequent.getIndex()).append(" else goto $").append(alternative.getIndex());
}
@Override
public void jumpIf(BinaryBranchingCondition cond, VariableReader first, VariableReader second,
BasicBlockReader consequent, BasicBlockReader alternative) {
sb.append("if @").append(first.getIndex()).append(" ");
append("if ").appendLocalVar(first).append(" ");
switch (cond) {
case EQUAL:
sb.append("==");
append("==");
break;
case REFERENCE_EQUAL:
sb.append("===");
append("===");
break;
case NOT_EQUAL:
sb.append("!=");
append("!=");
break;
case REFERENCE_NOT_EQUAL:
sb.append("!==");
append("!==");
break;
}
sb.append("@").append(second.getIndex()).append(" then goto $").append(consequent.getIndex())
appendLocalVar(second).append(" then goto $").append(consequent.getIndex())
.append(" else goto $").append(alternative.getIndex());
}
@Override
public void jump(BasicBlockReader target) {
sb.append("goto $").append(target.getIndex());
append("goto $").append(target.getIndex());
}
@Override
public void choose(VariableReader condition, List<? extends SwitchTableEntryReader> table,
BasicBlockReader defaultTarget) {
sb.append("switch @").append(condition.getIndex()).append(" ");
append("switch ").appendLocalVar(condition).append(" ");
for (int i = 0; i < table.size(); ++i) {
if (i > 0) {
sb.append(" ");
append(" ");
}
SwitchTableEntryReader entry = table.get(i);
sb.append("case ").append(entry.getCondition()).append(" goto $").append(entry.getTarget().getIndex());
append("case ").append(entry.getCondition()).append(" goto $").append(entry.getTarget().getIndex());
}
sb.append(" else goto $").append(defaultTarget.getIndex());
}
@Override
public void exit(VariableReader valueToReturn) {
sb.append("return");
append("return");
if (valueToReturn != null) {
sb.append(" @").append(valueToReturn.getIndex());
append(" ").appendLocalVar(valueToReturn);
}
}
@Override
public void raise(VariableReader exception) {
sb.append("throw @").append(exception.getIndex());
append("throw ").appendLocalVar(exception);
}
@Override
public void createArray(VariableReader receiver, ValueType itemType, VariableReader size) {
sb.append("@").append(receiver.getIndex()).append(" := new ");
escapeIdentifierIfNeeded(itemType.toString(), sb);
sb.append("[@").append(size.getIndex()).append(']');
appendLocalVar(receiver).append(" := new ").escapeIdentifierIfNeeded(itemType.toString())
.append("[").appendLocalVar(size).append(']');
}
@Override
public void createArray(VariableReader receiver, ValueType itemType, List<? extends VariableReader> dimensions) {
sb.append("@").append(receiver.getIndex()).append(" := newArray ");
escapeIdentifierIfNeeded(itemType.toString(), sb);
sb.append("[");
appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(itemType.toString());
append("[");
for (int i = 0; i < dimensions.size(); ++i) {
if (i > 0) {
sb.append(", ");
append(", ");
}
sb.append("@").append(dimensions.get(i).getIndex());
appendLocalVar(dimensions.get(i));
}
sb.append("]");
append("]");
}
@Override
public void create(VariableReader receiver, String type) {
sb.append("@").append(receiver.getIndex()).append(" := newArray ");
escapeIdentifierIfNeeded(type, sb);
appendLocalVar(receiver).append(" := newArray ").escapeIdentifierIfNeeded(type);
}
@Override
public void getField(VariableReader receiver, VariableReader instance, FieldReference field, ValueType fieldType) {
sb.append("@").append(receiver.getIndex()).append(" := field ");
escapeIdentifierIfNeeded(field.toString(), sb);
appendLocalVar(receiver).append(" := field ").escapeIdentifierIfNeeded(field.toString());
if (instance != null) {
sb.append(" @").append(instance.getIndex());
append(" ").appendLocalVar(instance);
}
sb.append(" as ");
escapeIdentifierIfNeeded(fieldType.toString(), sb);
append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
}
@Override
public void putField(VariableReader instance, FieldReference field, VariableReader value, ValueType fieldType) {
sb.append("field ");
escapeIdentifierIfNeeded(field.toString(), sb);
append("field ").escapeIdentifierIfNeeded(field.toString());
if (instance != null) {
sb.append(" @").append(instance.getIndex());
append(" ").appendLocalVar(instance);
}
sb.append(" := @").append(value.getIndex());
sb.append(" as ");
escapeIdentifierIfNeeded(fieldType.toString(), sb);
append(" := ").appendLocalVar(value).append(" as ").escapeIdentifierIfNeeded(fieldType.toString());
}
@Override
public void arrayLength(VariableReader receiver, VariableReader array) {
sb.append("@").append(receiver.getIndex()).append(" := lengthOf @").append(array.getIndex());
appendLocalVar(receiver).append(" := lengthOf ").appendLocalVar(array);
}
@Override
public void cloneArray(VariableReader receiver, VariableReader array) {
sb.append("@").append(receiver.getIndex()).append(" := clone @").append(array.getIndex());
appendLocalVar(receiver).append(" := clone ").appendLocalVar(array);
}
@Override
public void unwrapArray(VariableReader receiver, VariableReader array, ArrayElementType elementType) {
sb.append("@").append(receiver.getIndex()).append(" := data @").append(array.getIndex()).append(" as ")
appendLocalVar(receiver).append(" := data ").appendLocalVar(array).append(" as ")
.append(elementType.name().toLowerCase(Locale.ROOT));
}
@Override
public void getElement(VariableReader receiver, VariableReader array, VariableReader index,
ArrayElementType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(array.getIndex()).append("[@")
.append(index.getIndex()).append("]").append(" as " + type.name().toLowerCase(Locale.ROOT));
appendLocalVar(receiver).append(" := ").appendLocalVar(array).append("[").appendLocalVar(index).append("]")
.append(" as " + type.name().toLowerCase(Locale.ROOT));
}
@Override
public void putElement(VariableReader array, VariableReader index, VariableReader value, ArrayElementType type) {
sb.append("@").append(array.getIndex()).append("[@").append(index.getIndex()).append("] := @")
.append(value.getIndex()).append(" as " + type.name().toLowerCase());
appendLocalVar(array).append("[").appendLocalVar(index).append("] := ").appendLocalVar(value)
.append(" as " + type.name().toLowerCase(Locale.ROOT));
}
@Override
public void invoke(VariableReader receiver, VariableReader instance, MethodReference method,
List<? extends VariableReader> arguments, InvocationType type) {
if (receiver != null) {
sb.append("@").append(receiver.getIndex()).append(" := ");
appendLocalVar(receiver).append(" := ");
}
if (instance == null) {
sb.append("invokeStatic ");
append("invokeStatic ");
} else {
switch (type) {
case SPECIAL:
sb.append("invoke ");
append("invoke ");
break;
case VIRTUAL:
sb.append("invokeVirtual ");
append("invokeVirtual ");
break;
}
}
escapeIdentifierIfNeeded(method.toString(), sb);
escapeIdentifierIfNeeded(method.toString());
if (instance != null) {
sb.append(' ');
sb.append("@").append(instance.getIndex());
append(' ').appendLocalVar(instance);
}
for (int i = 0; i < arguments.size(); ++i) {
if (instance != null || i > 0) {
sb.append(",");
append(",");
}
sb.append(' ');
sb.append("@").append(arguments.get(i).getIndex());
append(' ').appendLocalVar(arguments.get(i));
}
}
@ -424,17 +455,17 @@ class InstructionStringifier implements InstructionReader {
List<? extends VariableReader> arguments, MethodHandle bootstrapMethod,
List<RuntimeConstant> bootstrapArguments) {
if (receiver != null) {
sb.append("@").append(receiver.getIndex()).append(" := ");
appendLocalVar(receiver).append(" := ");
}
if (instance != null) {
sb.append("@").append(instance.getIndex()).append(".");
appendLocalVar(instance).append(".");
}
sb.append(method.getName()).append("(");
sb.append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", ")));
sb.append(") ");
sb.append("[").append(convert(bootstrapMethod)).append('(');
sb.append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", ")));
sb.append(")");
append(method.getName()).append("(");
append(arguments.stream().map(arg -> "@" + arg.getIndex()).collect(Collectors.joining(", ")));
append(") ");
append("[").append(convert(bootstrapMethod)).append('(');
append(bootstrapArguments.stream().map(this::convert).collect(Collectors.joining(", ")));
append(")");
}
private String convert(MethodHandle handle) {
@ -488,28 +519,27 @@ class InstructionStringifier implements InstructionReader {
@Override
public void isInstance(VariableReader receiver, VariableReader value, ValueType type) {
sb.append("@").append(receiver.getIndex()).append(" := @").append(value.getIndex())
.append(" instanceOf ");
escapeIdentifierIfNeeded(type.toString(), sb);
appendLocalVar(receiver).append(" := ").appendLocalVar(value).append(" instanceOf ")
.escapeIdentifierIfNeeded(type.toString());
}
@Override
public void initClass(String className) {
sb.append("initClass ").append(className);
append("initClass ").append(className);
}
@Override
public void nullCheck(VariableReader receiver, VariableReader value) {
sb.append("@").append(receiver.getIndex()).append(" := nullCheck @").append(value.getIndex());
appendLocalVar(receiver).append(" := nullCheck ").appendLocalVar(value);
}
@Override
public void monitorEnter(VariableReader objectRef) {
sb.append("monitorEnter @").append(objectRef.getIndex());
append("monitorEnter ").appendLocalVar(objectRef);
}
@Override
public void monitorExit(VariableReader objectRef) {
sb.append("monitorExit @").append(objectRef.getIndex());
append("monitorExit ").appendLocalVar(objectRef);
}
}

View File

@ -24,13 +24,13 @@ public class ListingBuilder {
public String buildListing(ProgramReader program, String prefix) {
StringBuilder sb = new StringBuilder();
StringBuilder insnSb = new StringBuilder();
InstructionStringifier stringifier = new InstructionStringifier(insnSb);
InstructionStringifier stringifier = new InstructionStringifier(insnSb, program);
for (int i = 0; i < program.variableCount(); ++i) {
VariableReader var = program.variableAt(i);
if (var == null || var.getDebugName() == null) {
continue;
}
sb.append(prefix).append("var @").append(i);
sb.append(prefix).append("var @").append(stringifier.getVariableLabel(i));
sb.append('\n');
}
for (int i = 0; i < program.basicBlockCount(); ++i) {
@ -41,20 +41,21 @@ public class ListingBuilder {
}
if (block.getExceptionVariable() != null) {
sb.append(" @").append(block.getExceptionVariable().getIndex()).append(" = exception\n");
sb.append(" ").append(stringifier.getVariableLabel(block.getExceptionVariable().getIndex()))
.append(" = exception\n");
}
for (PhiReader phi : block.readPhis()) {
sb.append(prefix).append(" ");
sb.append("@").append(phi.getReceiver().getIndex()).append(" := phi ");
sb.append("@").append(stringifier.getVariableLabel(phi.getReceiver().getIndex())).append(" := phi ");
List<? extends IncomingReader> incomings = phi.readIncomings();
for (int j = 0; j < incomings.size(); ++j) {
if (j > 0) {
sb.append(", ");
}
IncomingReader incoming = incomings.get(j);
sb.append("@").append(incoming.getValue().getIndex()).append(" from ")
.append("$").append(incoming.getSource().getIndex());
sb.append("@").append(stringifier.getVariableLabel(incoming.getValue().getIndex()))
.append(" from ").append("$").append(incoming.getSource().getIndex());
}
sb.append("\n");
}
@ -86,8 +87,10 @@ public class ListingBuilder {
sb.append(" goto $").append(tryCatch.getHandler().getIndex());
sb.append("\n");
for (TryCatchJointReader joint : tryCatch.readJoints()) {
sb.append(" @").append(joint.getReceiver().getIndex()).append(" := ephi ");
sb.append(joint.readSourceVariables().stream().map(sourceVar -> "@" + sourceVar.getIndex())
sb.append(" @").append(stringifier.getVariableLabel(joint.getReceiver().getIndex()))
.append(" := ephi ");
sb.append(joint.readSourceVariables().stream()
.map(sourceVar -> "@" + stringifier.getVariableLabel(sourceVar.getIndex()))
.collect(Collectors.joining(", ")));
sb.append("\n");
}

View File

@ -72,6 +72,7 @@ import org.teavm.model.instructions.MonitorEnterInstruction;
import org.teavm.model.instructions.MonitorExitInstruction;
import org.teavm.model.instructions.NegateInstruction;
import org.teavm.model.instructions.NullCheckInstruction;
import org.teavm.model.instructions.NullConstantInstruction;
import org.teavm.model.instructions.NumericOperandType;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
@ -349,6 +350,13 @@ public class ListingParser {
case IDENTIFIER: {
String keyword = (String) lexer.getTokenValue();
switch (keyword) {
case "null": {
lexer.nextToken();
NullConstantInstruction insn = new NullConstantInstruction();
insn.setReceiver(receiver);
addInstruction(insn);
break;
}
case "phi":
lexer.nextToken();
parsePhi(receiver);

View File

@ -0,0 +1,63 @@
/*
* Copyright 2016 Alexey Andreev.
*
* 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 org.teavm.model.util;
import org.teavm.common.DominatorTree;
import org.teavm.common.Graph;
import org.teavm.common.GraphUtils;
import org.teavm.model.BasicBlock;
import org.teavm.model.Program;
public class DominatorWalker {
private Program program;
private Graph domGraph;
public DominatorWalker(Program program) {
this.program = program;
Graph cfg = ProgramUtils.buildControlFlowGraph(program);
DominatorTree dom = GraphUtils.buildDominatorTree(cfg);
domGraph = GraphUtils.buildDominatorGraph(dom, cfg.size());
}
public <T> void walk(DominatorWalkerCallback<T> callback) {
int[] stack = new int[program.basicBlockCount() * 2];
Object[] stateStack = new Object[stack.length];
boolean[] backward = new boolean[stack.length];
int head = 1;
while (head > 0) {
int node = stack[--head];
BasicBlock block = program.basicBlockAt(node);
if (backward[head]) {
@SuppressWarnings("unchecked")
T state = (T) stateStack[head];
callback.endVisit(block, state);
} else if (callback.filter(block)) {
stack[head] = node;
backward[head] = true;
stateStack[head] = callback.visit(block);
head++;
int[] successors = domGraph.outgoingEdges(node);
for (int i = successors.length - 1; i >= 0; --i) {
stack[head] = successors[i];
backward[head] = false;
head++;
}
}
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2016 Alexey Andreev.
*
* 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 org.teavm.model.util;
import org.teavm.model.BasicBlock;
/**
* An object that receives information from {@link DominatorWalker}
*
* @param <T> type of state that can be saved for each visited node.
*/
public interface DominatorWalkerCallback<T> {
/**
* Called before visiting block. This method should tell whether this block and all of its descendant blocks
* should be visited.
*/
default boolean filter(BasicBlock block) {
return true;
}
/**
* Visits block and returns visit state. This state is saved by walker and then passed to
* {@link #endVisit(BasicBlock, Object)} after all of this block's descendants visited.
*
* @return state that further will be passed to {@link #endVisit(BasicBlock, Object)}
*/
T visit(BasicBlock block);
default void endVisit(BasicBlock block, T state) {
}
}

View File

@ -105,7 +105,7 @@ public class PhiUpdater {
return variableToSourceMap.get(var);
}
public List<Phi> getSynthesizedPhisBy() {
public List<Phi> getSynthesizedPhis() {
return synthesizedPhis;
}
@ -232,6 +232,7 @@ public class PhiUpdater {
for (Phi phi : synthesizedPhisByBlock.get(index)) {
Variable var = program.createVariable();
var.setDebugName(phi.getReceiver().getDebugName());
var.setLabel(phi.getReceiver().getLabel());
mapVariable(phi.getReceiver().getIndex(), var);
phisByReceiver.put(var.getIndex(), phi);
phi.setReceiver(var);
@ -269,6 +270,7 @@ public class PhiUpdater {
for (TryCatchJoint joint : synthesizedJointsByBlock.get(index).get(i)) {
Variable var = program.createVariable();
var.setDebugName(joint.getReceiver().getDebugName());
var.setLabel(joint.getReceiver().getLabel());
mapVariable(joint.getReceiver().getIndex(), var);
joint.setReceiver(var);
jointsByReceiver.put(var.getIndex(), joint);
@ -277,7 +279,8 @@ public class PhiUpdater {
variableMap = regularVariableMap;
int[] successors = domGraph.outgoingEdges(index);
for (int successor : successors) {
for (int j = successors.length - 1; j >= 0; --j) {
int successor = successors[j];
Task next = new Task();
next.variables = (catchSuccessors.contains(successor) ? catchVariableMap : variableMap).clone();
next.block = program.basicBlockAt(successor);
@ -473,7 +476,10 @@ public class PhiUpdater {
if (!usedDefinitions[var.getIndex()]) {
usedDefinitions[var.getIndex()] = true;
} else {
Variable old = var;
var = program.createVariable();
var.setDebugName(old.getDebugName());
var.setLabel(old.getLabel());
}
return var;

View File

@ -16,8 +16,10 @@
package org.teavm.model.util;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teavm.common.Graph;
import org.teavm.common.GraphBuilder;
import org.teavm.model.BasicBlock;
@ -219,4 +221,24 @@ public final class ProgramUtils {
}
return places;
}
public static void makeUniqueLabels(Program program) {
Set<String> occupiedLabels = new HashSet<>();
for (int i = 0; i < program.variableCount(); ++i) {
Variable var = program.variableAt(i);
if (var.getLabel() == null) {
continue;
}
String suggestedName = var.getLabel();
if (!occupiedLabels.add(suggestedName)) {
int suffix = 1;
String base = suggestedName + "_";
do {
suggestedName = base + suffix++;
} while (!occupiedLabels.add(suggestedName));
}
var.setLabel(suggestedName);
}
}
}

View File

@ -0,0 +1,155 @@
/*
* Copyright 2016 Alexey Andreev.
*
* 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 org.teavm.model.analysis.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.carrotsearch.hppc.IntIntMap;
import com.carrotsearch.hppc.IntIntOpenHashMap;
import com.carrotsearch.hppc.ObjectByteMap;
import com.carrotsearch.hppc.ObjectByteOpenHashMap;
import com.carrotsearch.hppc.ObjectIntMap;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.analysis.NullnessInformation;
import org.teavm.model.text.ListingBuilder;
import org.teavm.model.text.ListingParseException;
import org.teavm.model.text.ListingParser;
import org.teavm.model.util.ProgramUtils;
public class NullnessAnalysisTest {
@Rule
public TestName name = new TestName();
private static final String NOT_NULL_DIRECTIVE = "// NOT_NULL ";
private static final String NULLABLE_DIRECTIVE = "// NULLABLE ";
@Test
public void simple() {
test();
}
@Test
public void phiJoin() {
test();
}
@Test
public void branch() {
test();
}
private void test() {
String baseName = "model/analysis/" + name.getMethodName();
String originalResourceName = baseName + ".original.txt";
String extendedResourceName = baseName + ".extended.txt";
Program originalProgram = parseResource(originalResourceName);
Program extendedProgram = parseResource(extendedResourceName);
ListingBuilder listingBuilder = new ListingBuilder();
String listingBeforeExtension = listingBuilder.buildListing(originalProgram, "");
NullnessInformation information = NullnessInformation.build(originalProgram,
new MethodDescriptor("foo", ValueType.VOID));
ProgramUtils.makeUniqueLabels(originalProgram);
String actualListing = listingBuilder.buildListing(originalProgram, "");
String expectedListing = listingBuilder.buildListing(extendedProgram, "");
assertEquals(expectedListing, actualListing);
ObjectByteMap<String> expectedNullness = extractExpectedNullness(extendedResourceName);
Map<String, Variable> variablesByLabel = variablesByLabel(originalProgram);
for (ObjectCursor<String> varNameCursor : expectedNullness.keys()) {
String varName = varNameCursor.value;
Variable var = variablesByLabel.get(varName);
assertNotNull("Variable " + varName + " is missing", var);
boolean notNull = expectedNullness.get(varName) != 0;
assertEquals("Variable " + varName + " non-null", notNull, information.isNotNull(var));
}
information.dispose();
String listingAfterDispose = listingBuilder.buildListing(originalProgram, "");
assertEquals(listingBeforeExtension, listingAfterDispose);
}
private Program parseResource(String name) {
ClassLoader classLoader = NullnessAnalysisTest.class.getClassLoader();
try (InputStream input = classLoader.getResourceAsStream(name);
Reader reader = new InputStreamReader(input, "UTF-8")) {
return new ListingParser().parse(reader);
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ListingParseException e) {
throw new RuntimeException("at " + e.getIndex() + "", e);
}
}
private ObjectByteMap<String> extractExpectedNullness(String name) {
ClassLoader classLoader = NullnessAnalysisTest.class.getClassLoader();
try (InputStream input = classLoader.getResourceAsStream(name);
BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8"))) {
ObjectByteMap<String> result = new ObjectByteOpenHashMap<>();
while (true) {
String line = reader.readLine();
if (line == null) {
break;
}
int index = line.indexOf(NOT_NULL_DIRECTIVE);
if (index >= 0) {
String variable = line.substring(index + NOT_NULL_DIRECTIVE.length()).trim();
result.put(variable, (byte) 1);
}
index = line.indexOf(NULLABLE_DIRECTIVE);
if (index >= 0) {
String variable = line.substring(index + NULLABLE_DIRECTIVE.length()).trim();
result.put(variable, (byte) 0);
}
}
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Map<String, Variable> variablesByLabel(Program program) {
Map<String, Variable> result = new HashMap<>();
for (int i = 0; i < program.variableCount(); ++i) {
Variable var = program.variableAt(i);
if (var.getLabel() != null) {
result.put(var.getLabel(), var);
}
}
return result;
}
}

View File

@ -0,0 +1,21 @@
var @this as this
$start
@v := invokeStatic `Foo.get()LFoo;`
if @v === null then goto $ifNull else goto $ifNotNull
$ifNull
@v_1 := null
invokeVirtual `Foo.bar()V` @v_1
@v_2 := nullCheck @v_1
goto $join
$ifNotNull
@v_3 := nullCheck @v
invokeVirtual `Foo.baz()V` @v_3
goto $join
$join
return
// NULLABLE v
// NULLABLE v_1
// NOT_NULL v_2
// NOT_NULL v_3

View File

@ -0,0 +1,13 @@
var @this as this
$start
@v := invokeStatic `Foo.get()LFoo;`
if @v === null then goto $ifNull else goto $ifNotNull
$ifNull
invokeVirtual `Foo.bar()V` @v
goto $join
$ifNotNull
invokeVirtual `Foo.baz()V` @v
goto $join
$join
return

View File

@ -0,0 +1,21 @@
$start
if @cond === null then goto $ifNull else goto $ifNotNull
$ifNull
@cond_1 := null
@a := 'qwe'
goto $join
$ifNotNull
@cond_2 := nullCheck @cond
@b := invokeStatic `org.test.Foo.bar()Ljava/lang/String;`
@b_1 := nullCheck @b
goto $join
$join
@c := phi @a from $ifNull, @b from $ifNotNull
@d := phi @a from $ifNull, @b_1 from $ifNotNull
return @c
// NULLABLE: b
// NOT_NULL: b_1
// NOT_NULL: a
// NULLABLE: c
// NOT_NULL: d

View File

@ -0,0 +1,13 @@
$start
if @cond === null then goto $ifNull else goto $ifNotNull
$ifNull
@a := 'qwe'
goto $join
$ifNotNull
@b := invokeStatic `org.test.Foo.bar()Ljava/lang/String;`
@b_1 := nullCheck @b
goto $join
$join
@c := phi @a from $ifNull, @b from $ifNotNull
@d := phi @a from $ifNull, @b_1 from $ifNotNull
return @c

View File

@ -0,0 +1,10 @@
var @this as this
$0
@a := invokeStatic `Foo.bar()Ljava/lang/Object;`
invokeVirtual `java.lang.Object.toString()Ljava/lang/String;` @a
@a_1 := nullCheck @a
return @a_1
// NULLABLE a
// NOT_NULL a_1

View File

@ -0,0 +1,6 @@
var @this as this
$0
@a := invokeStatic `Foo.bar()Ljava/lang/Object;`
invokeVirtual `java.lang.Object.toString()Ljava/lang/String;` @a
return @a