CFGBuilder javadoc (IDEA-CR-23268)

This commit is contained in:
Tagir Valeev
2017-07-27 11:57:01 +07:00
parent 66eabcc11e
commit cd69b1a33d
@@ -26,6 +26,7 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ObjectUtils;
import one.util.streamex.StreamEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
@@ -47,42 +48,115 @@ public class CFGBuilder {
myAnalyzer = analyzer;
}
/**
* Generate instructions to push unknown DfaValue on stack.
* <p>
* Stack before: ...
* <p>
* Stack after: ... unknown
*
* @return this builder
*/
public CFGBuilder pushUnknown() {
myAnalyzer.pushUnknown();
return this;
}
/**
* Generate instructions to push null DfaValue on stack.
* <p>
* Stack before: ...
* <p>
* Stack after: ... null
*
* @return this builder
*/
public CFGBuilder pushNull() {
myAnalyzer.addInstruction(new PushInstruction(getFactory().getConstFactory().getNull(), null));
return this;
}
/**
* Generate instructions to evaluate given expression and push its result on stack.
* <p>
* Stack before: ...
* <p>
* Stack after: ... expression_result
*
* @param expression expression to evaluate
* @return this builder
*/
public CFGBuilder pushExpression(PsiExpression expression) {
expression.accept(myAnalyzer);
return this;
}
/**
* Generate instructions to push given variable on stack for subsequent write.
* <p>
* Stack before: ...
* <p>
* Stack after: ... variable
*
* @param variable to push
* @return this builder
*/
public CFGBuilder pushVariable(PsiVariable variable) {
myAnalyzer.addInstruction(
new PushInstruction(getFactory().getVarFactory().createVariableValue(variable, false), null, true));
return this;
}
/**
* Generate instructions to push given DfaValue on stack.
* <p>
* Stack before: ...
* <p>
* Stack after: ... value
*
* @param value value to push
* @return this builder
*/
public CFGBuilder push(DfaValue value) {
myAnalyzer.addInstruction(new PushInstruction(value, null));
return this;
}
/**
* Generate instructions to pop single DfaValue from stack
* <p>
* Stack before: ... value
* <p>
* Stack after: ...
*
* @return this builder
*/
public CFGBuilder pop() {
myAnalyzer.addInstruction(new PopInstruction());
return this;
}
/**
* Generate instructions to duplicate top stack value
* <p>
* Stack before: ... value
* <p>
* Stack after: ... value value
*
* @return this builder
*/
public CFGBuilder dup() {
myAnalyzer.addInstruction(new DupInstruction());
return this;
}
/**
* Generate instructions to dereference check for the top stack value.
* Stack is unchanged.
*
* @param referenceExpression a PSI anchor to report possible NPE if top stack value is nullable
* @return this builder
*/
public CFGBuilder dereferenceCheck(PsiReferenceExpression referenceExpression) {
if (referenceExpression != null) {
myAnalyzer.addInstruction(new DupInstruction());
@@ -91,21 +165,84 @@ public class CFGBuilder {
return this;
}
/**
* Generate instructions to pop given number of stack values, then push some or all of popped values referred by indices,
* possibly duplicating them
* <p>
* E.g. {@code splice(2, 0, 1, 0)} will change "... val1 val2" stack to "... val2 val1 val2".
* Stack depth is increased by {@code replacement.length - count}.
*
* @param count number of values to pop
* @param replacement replacement indices from 0 to {@code count-1}. Index 0 = top stack value, index 1 = next value and so on.
* @return this builder
*/
public CFGBuilder splice(int count, int... replacement) {
myAnalyzer.addInstruction(new SpliceInstruction(count, replacement));
return this;
}
/**
* Generate instructions to swap two top stack values
* <p>
* Stack before: ... val1 val2
* <p>
* Stack after: ... val2 val1
*
* @return this builder
*/
public CFGBuilder swap() {
myAnalyzer.addInstruction(new SwapInstruction());
return this;
}
/**
* Generate instructions to invoke the method associated with given method call assuming that method arguments and qualifier
* are already on stack. If vararg call is specified, vararg arguments should be placed as is, without packing into array,
* so number of arguments may differ from number of method parameters.
* <p>
* Stack before: ... qualifier arg1 arg2 ... argN
* <p>
* Stack after: ... return value
* <p>
* Note that qualifier must be present even if method is static (use {@link #pushUnknown()}). Similarly, return value will be pushed
* on stack always, even if method is void.
*
* @param call a method call to generate invocation upon
* @return this builder
*/
public CFGBuilder invoke(PsiMethodCallExpression call) {
myAnalyzer.addBareCall(call, call.getMethodExpression());
return this;
}
/**
* Generate instructions to compare two values on top of stack with given relation operation (e.g. {@link JavaTokenType#GT}).
* <p>
* Stack before: ... val1 val2
* <p>
* Stack after: ... result_of_val1_relation_val2
*
* @param relation relation to use for comparison
* @return this builder
*/
private CFGBuilder compare(IElementType relation) {
myAnalyzer.addInstruction(new BinopInstruction(relation, null, myAnalyzer.getContext().getProject()));
return this;
}
/**
* Generate instructions to start a conditional block based on stack top value, consuming this value
* <p>
* Stack before: ... condition
* <p>
* Stack after: ...
* <p>
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
* Nested conditional blocks are acceptable.
*
* @param value a value condition must have to visit conditional block
* @return this builder
*/
public CFGBuilder ifConditionIs(boolean value) {
ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, value, null);
myBranches.add(gotoInstruction);
@@ -113,16 +250,74 @@ public class CFGBuilder {
return this;
}
/**
* Generate instructions to start a conditional block based on result of comparison of
* two stack values with given relation (e.g. {@link JavaTokenType#GT}), consuming these values.
* <p>
* Stack before: ... val1 val2
* <p>
* Stack after: ...
* <p>
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
* Nested conditional blocks are acceptable.
*
* @param relation a relation to use to compare two stack values. Conditional block will be executed if "val1 relation val2" is true.
* @return this builder
*/
public CFGBuilder ifCondition(IElementType relation) {
return compare(relation).ifConditionIs(true);
}
/**
* Generate instructions to start a conditional block which is executed if top stack value is not null.
* <p>
* Stack before: ... value
* <p>
* Stack after: ...
* <p>
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
* Nested conditional blocks are acceptable.
*
* @return this builder
*/
public CFGBuilder ifNotNull() {
return pushNull().ifCondition(JavaTokenType.NE);
}
/**
* Generate instructions to start a conditional block which is executed if top stack value is null.
* <p>
* Stack before: ... value
* <p>
* Stack after: ...
* <p>
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
* Nested conditional blocks are acceptable.
*
* @return this builder
*/
public CFGBuilder ifNull() {
return pushNull().ifCondition(JavaTokenType.EQEQ);
}
/**
* Generate instructions to finish a conditional block started with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)},
* {@link #ifNull()} or {@link #ifNotNull()}. Stack is unchanged.
*
* @return this builder
*/
public CFGBuilder endIf() {
myBranches.removeLast().setOffset(myAnalyzer.getInstructionCount());
return this;
}
private CFGBuilder compare(IElementType relation) {
myAnalyzer.addInstruction(new BinopInstruction(relation, null, myAnalyzer.getContext().getProject()));
return this;
}
/**
* Generate instructions to finish a "then-branch" and start an "else-branch" of a conditional block started
* with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)}, {@link #ifNull()} or {@link #ifNotNull()}.
* Stack is unchanged.
*
* @return this builder
*/
public CFGBuilder elseBranch() {
GotoInstruction gotoInstruction = new GotoInstruction(null);
myAnalyzer.addInstruction(gotoInstruction);
@@ -131,18 +326,12 @@ public class CFGBuilder {
return this;
}
public CFGBuilder ifCondition(IElementType relation) {
return compare(relation).ifConditionIs(true);
}
public CFGBuilder ifNotNull() {
return pushNull().ifCondition(JavaTokenType.NE);
}
public CFGBuilder ifNull() {
return pushNull().ifCondition(JavaTokenType.EQEQ);
}
/**
* Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #endWhileUnknown()}.
* Nested loops are acceptable.
*
* @return this builder
*/
public CFGBuilder doWhile() {
ConditionalGotoInstruction jump = new ConditionalGotoInstruction(null, false, null);
jump.setOffset(myAnalyzer.getInstructionCount());
@@ -150,48 +339,114 @@ public class CFGBuilder {
return this;
}
/**
* Generate instructions to end a loop started via {@link #doWhile()} by unknown condition. Stack is unchanged.
*
* @return this builder
*/
public CFGBuilder endWhileUnknown() {
pushUnknown();
myAnalyzer.addInstruction((ConditionalGotoInstruction)myBranches.removeLast());
return this;
}
/**
* Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type.
* <p>
* Stack before: ... value
* <p>
* Stack after: ... boxed_or_unboxed_value
*
* @param expression an expression which result is placed on the top of stack
* @param expectedType an expected type
*
* @return this builder
*/
public CFGBuilder boxUnbox(PsiExpression expression, PsiType expectedType) {
myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expectedType);
return this;
}
/**
* Generate instructions to box or unbox stack top value if necessary to satisfy the specified expected type.
* <p>
* Stack before: ... value
* <p>
* Stack after: ... boxed_or_unboxed_value
*
* @param expression an expression which is used to anchor instructions so issued warnings can point to this expression
* @param expressionType an actual type of the expression on top of stack
* @param expectedType an expected type
*
* @return this builder
*/
public CFGBuilder boxUnbox(PsiExpression expression, PsiType expressionType, PsiType expectedType) {
myAnalyzer.generateBoxingUnboxingInstructionFor(expression, expressionType, expectedType);
return this;
}
/**
* Generate instructions to flush known values of non-final fields of mutable classes.
*
* @return this builder
*/
public CFGBuilder flushFields() {
myAnalyzer.addInstruction(new FlushVariableInstruction(null));
return this;
}
/**
* Generate instructions to check that stack top value is not null issuing a warning like "argument is nullable" if
* this is not satisfied. Stack is unchanged.
*
* @param expression an anchor expression to bind a warning to
* @return this builder
*/
public CFGBuilder checkNotNull(PsiExpression expression) {
myAnalyzer.addInstruction(new CheckNotNullInstruction(expression));
return this;
}
/**
* Generate instructions to assign top stack value to the second stack value
* (usually pushed via {@link #pushVariable(PsiVariable)}).
* <p>
* Stack before: ... variable_for_write value
* <p>
* Stack after: ... variable
*
* @return this builder
*/
public CFGBuilder assign() {
myAnalyzer.addInstruction(new AssignInstruction(null, null));
return this;
}
/**
* Generate instructions to assign top stack value to the specified variable
* <p>
* Stack before: ... value
* <p>
* Stack after: ... variable
*
* @return this builder
*/
public CFGBuilder assignTo(PsiVariable var) {
return pushVariable(var).swap().assign();
}
/**
* Returns a {@link DfaValueFactory} associated with current control flow.
*
* @return a {@link DfaValueFactory} associated with current control flow.
*/
public DfaValueFactory getFactory() {
return myAnalyzer.getFactory();
}
/**
* Generate instructions to evaluate functional expression (but not invoke the function itself
* -- see {@link #invokeFunction(int, PsiExpression)}). After this call stack is left intact.
* -- see {@link #invokeFunction(int, PsiExpression)}). Stack is unchanged.
*
* @param functionalExpression a functional expression to evaluate
* @return this builder
@@ -305,13 +560,26 @@ public class CFGBuilder {
return this;
}
public PsiVariable createTempVariable(PsiType type) {
/**
* Create a temporary {@link PsiVariable} (not declared in the original code) to be used within this control flow.
*
* @param type a type of variable to create
* @return newly created variable
*/
@NotNull
public PsiVariable createTempVariable(@Nullable PsiType type) {
if(type == null) {
type = PsiType.VOID;
}
return new LightVariableBuilder<>("tmp$" + myAnalyzer.getInstructionCount(), type, myAnalyzer.getContext());
}
/**
* A convenient method to chain specific builder operation
*
* @param operation to execute on this builder
* @return this builder
*/
public CFGBuilder chain(Consumer<CFGBuilder> operation) {
operation.accept(this);
return this;