diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java index c080a212f1a0..74636fe432c2 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/CFGBuilder.java @@ -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. + *
+ * Stack before: ... + *
+ * Stack after: ... unknown + * + * @return this builder + */ public CFGBuilder pushUnknown() { myAnalyzer.pushUnknown(); return this; } + /** + * Generate instructions to push null DfaValue on stack. + *
+ * Stack before: ... + *
+ * 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. + *
+ * Stack before: ... + *
+ * 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. + *
+ * Stack before: ... + *
+ * 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. + *
+ * Stack before: ... + *
+ * 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 + *
+ * Stack before: ... value + *
+ * Stack after: ... + * + * @return this builder + */ public CFGBuilder pop() { myAnalyzer.addInstruction(new PopInstruction()); return this; } + /** + * Generate instructions to duplicate top stack value + *
+ * Stack before: ... value + *
+ * 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 + *
+ * 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 + *
+ * Stack before: ... val1 val2 + *
+ * 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. + *
+ * Stack before: ... qualifier arg1 arg2 ... argN + *
+ * Stack after: ... return value + *
+ * 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}). + *
+ * Stack before: ... val1 val2 + *
+ * 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 + *
+ * Stack before: ... condition + *
+ * Stack after: ... + *
+ * 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. + *
+ * Stack before: ... val1 val2 + *
+ * Stack after: ... + *
+ * 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. + *
+ * Stack before: ... value + *
+ * Stack after: ... + *
+ * 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. + *
+ * Stack before: ... value + *
+ * Stack after: ... + *
+ * 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. + *
+ * Stack before: ... value + *
+ * 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. + *
+ * Stack before: ... value + *
+ * 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)}). + *
+ * Stack before: ... variable_for_write value + *
+ * 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 + *
+ * Stack before: ... value + *
+ * 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