mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Unroll short Stream.of(...) sequences (up to three elements)
Fixes IDEA-189143 Support unrolling of Stream.of(a, b, c)
This commit is contained in:
@@ -25,6 +25,7 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -41,13 +42,18 @@ import java.util.function.Consumer;
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public class CFGBuilder {
|
||||
private final ControlFlowAnalyzer myAnalyzer;
|
||||
private final Deque<JumpInstruction> myBranches = new ArrayDeque<>();
|
||||
private final Deque<Runnable> myBranches = new ArrayDeque<>();
|
||||
private final Map<PsiExpression, DfaVariableValue> myMethodRefQualifiers = new HashMap<>();
|
||||
|
||||
CFGBuilder(ControlFlowAnalyzer analyzer) {
|
||||
myAnalyzer = analyzer;
|
||||
}
|
||||
|
||||
private CFGBuilder add(Instruction instruction) {
|
||||
myAnalyzer.addInstruction(instruction);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to push unknown DfaValue on stack.
|
||||
* <p>
|
||||
@@ -72,8 +78,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder pushNull() {
|
||||
myAnalyzer.addInstruction(new PushInstruction(getFactory().getConstFactory().getNull(), null));
|
||||
return this;
|
||||
return add(new PushInstruction(getFactory().getConstFactory().getNull(), null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,8 +107,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder pushForWrite(DfaVariableValue variable) {
|
||||
myAnalyzer.addInstruction(new PushInstruction(variable, null, true));
|
||||
return this;
|
||||
return add(new PushInstruction(variable, null, true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,8 +121,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder push(DfaValue value) {
|
||||
myAnalyzer.addInstruction(new PushInstruction(value, null));
|
||||
return this;
|
||||
return add(new PushInstruction(value, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,8 +134,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder pop() {
|
||||
myAnalyzer.addInstruction(new PopInstruction());
|
||||
return this;
|
||||
return add(new PopInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,8 +147,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder dup() {
|
||||
myAnalyzer.addInstruction(new DupInstruction());
|
||||
return this;
|
||||
return add(new DupInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -161,8 +162,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder splice(int count, int... replacement) {
|
||||
myAnalyzer.addInstruction(new SpliceInstruction(count, replacement));
|
||||
return this;
|
||||
return add(new SpliceInstruction(count, replacement));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,8 +175,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder swap() {
|
||||
myAnalyzer.addInstruction(new SwapInstruction());
|
||||
return this;
|
||||
return add(new SwapInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,8 +188,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder objectOf() {
|
||||
myAnalyzer.addInstruction(new ObjectOfInstruction());
|
||||
return this;
|
||||
return add(new ObjectOfInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -204,8 +202,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder isInstance(PsiMethodCallExpression anchor) {
|
||||
myAnalyzer.addInstruction(new InstanceofInstruction(anchor));
|
||||
return this;
|
||||
return add(new InstanceofInstruction(anchor));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,8 +236,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
private CFGBuilder compare(IElementType relation) {
|
||||
myAnalyzer.addInstruction(new BinopInstruction(relation, null, PsiType.BOOLEAN));
|
||||
return this;
|
||||
return add(new BinopInstruction(relation, null, PsiType.BOOLEAN));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,7 +246,7 @@ public class CFGBuilder {
|
||||
* <p>
|
||||
* Stack after: ...
|
||||
* <p>
|
||||
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
|
||||
* The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside.
|
||||
* Nested conditional blocks are acceptable.
|
||||
*
|
||||
* @param value a value condition must have to visit conditional block
|
||||
@@ -258,9 +254,8 @@ public class CFGBuilder {
|
||||
*/
|
||||
public CFGBuilder ifConditionIs(boolean value) {
|
||||
ConditionalGotoInstruction gotoInstruction = new ConditionalGotoInstruction(null, value, null);
|
||||
myBranches.add(gotoInstruction);
|
||||
myAnalyzer.addInstruction(gotoInstruction);
|
||||
return this;
|
||||
myBranches.add(() -> gotoInstruction.setOffset(myAnalyzer.getInstructionCount()));
|
||||
return add(gotoInstruction);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +266,7 @@ public class CFGBuilder {
|
||||
* <p>
|
||||
* Stack after: ...
|
||||
* <p>
|
||||
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
|
||||
* The conditional block must end with {@link #end()} 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.
|
||||
@@ -288,7 +283,7 @@ public class CFGBuilder {
|
||||
* <p>
|
||||
* Stack after: ...
|
||||
* <p>
|
||||
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
|
||||
* The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside.
|
||||
* Nested conditional blocks are acceptable.
|
||||
*
|
||||
* @return this builder
|
||||
@@ -304,7 +299,7 @@ public class CFGBuilder {
|
||||
* <p>
|
||||
* Stack after: ...
|
||||
* <p>
|
||||
* The conditional block must end with {@link #endIf()} and may contain one {@link #elseBranch()} inside.
|
||||
* The conditional block must end with {@link #end()} and may contain one {@link #elseBranch()} inside.
|
||||
* Nested conditional blocks are acceptable.
|
||||
*
|
||||
* @return this builder
|
||||
@@ -314,13 +309,12 @@ public class CFGBuilder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to finish a conditional block started with {@link #ifCondition(IElementType)}, {@link #ifConditionIs(boolean)},
|
||||
* {@link #ifNull()} or {@link #ifNotNull()}. Stack is unchanged.
|
||||
* Generate instructions to finish a conditional block or a loop. Stack is unchanged.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder endIf() {
|
||||
myBranches.removeLast().setOffset(myAnalyzer.getInstructionCount());
|
||||
public CFGBuilder end() {
|
||||
myBranches.removeLast().run();
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -333,33 +327,21 @@ public class CFGBuilder {
|
||||
*/
|
||||
public CFGBuilder elseBranch() {
|
||||
GotoInstruction gotoInstruction = new GotoInstruction(null);
|
||||
myAnalyzer.addInstruction(gotoInstruction);
|
||||
endIf();
|
||||
myBranches.add(gotoInstruction);
|
||||
add(gotoInstruction).end();
|
||||
myBranches.add(() -> gotoInstruction.setOffset(myAnalyzer.getInstructionCount()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #endWhileUnknown()}.
|
||||
* Generate instructions to start a loop. Stack is unchanged. Loop must be terminated via {@link #end()}.
|
||||
* Nested loops are acceptable.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder doWhile() {
|
||||
public CFGBuilder doWhileUnknown() {
|
||||
ConditionalGotoInstruction jump = new ConditionalGotoInstruction(null, false, null);
|
||||
jump.setOffset(myAnalyzer.getInstructionCount());
|
||||
myBranches.add(jump);
|
||||
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());
|
||||
myBranches.add(() -> pushUnknown().add(jump));
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -404,8 +386,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder flushFields() {
|
||||
myAnalyzer.addInstruction(new FlushFieldsInstruction());
|
||||
return this;
|
||||
return add(new FlushFieldsInstruction());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -417,8 +398,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public <T extends PsiElement> CFGBuilder checkNotNull(T expression, NullabilityProblemKind<T> kind) {
|
||||
myAnalyzer.addInstruction(new CheckNotNullInstruction(kind.problem(expression)));
|
||||
return this;
|
||||
return add(new CheckNotNullInstruction(kind.problem(expression)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -432,8 +412,7 @@ public class CFGBuilder {
|
||||
* @return this builder
|
||||
*/
|
||||
public CFGBuilder assign() {
|
||||
myAnalyzer.addInstruction(new AssignInstruction(null, null));
|
||||
return this;
|
||||
return add(new AssignInstruction(null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -596,7 +575,7 @@ public class CFGBuilder {
|
||||
if (qualifier == null) return false;
|
||||
PsiType type = qualifier.getOperand().getType();
|
||||
push(getFactory().createTypeValue(type, Nullness.NOT_NULL));
|
||||
myAnalyzer.addInstruction(new InstanceofInstruction(methodRef, null, type));
|
||||
add(new InstanceofInstruction(methodRef, null, type));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -649,6 +628,45 @@ public class CFGBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CFGBuilder loopOver(PsiExpression[] expressions, DfaVariableValue targetVariable) {
|
||||
DfaValueFactory factory = getFactory();
|
||||
if (expressions.length > ControlFlowAnalyzer.MAX_UNROLL_SIZE) {
|
||||
DfaValue loopElement = null;
|
||||
for (PsiExpression expression : expressions) {
|
||||
pushExpression(expression);
|
||||
DfaValue expressionValue = factory.createValue(expression);
|
||||
if (expressionValue == null) {
|
||||
expressionValue = factory.createTypeValue(expression.getType(), NullnessUtil.getExpressionNullness(expression));
|
||||
}
|
||||
loopElement = loopElement == null ? expressionValue : loopElement.union(expressionValue);
|
||||
pop();
|
||||
}
|
||||
ConditionalGotoInstruction condGoto = new ConditionalGotoInstruction(null, false, null);
|
||||
condGoto.setOffset(myAnalyzer.getInstructionCount());
|
||||
myBranches.add(() -> pushUnknown().add(condGoto));
|
||||
pushForWrite(targetVariable).push(loopElement).assign();
|
||||
} else {
|
||||
push(factory.getConstFactory().getSentinel());
|
||||
for (PsiExpression expression : expressions) {
|
||||
pushExpression(expression);
|
||||
}
|
||||
// Revert order
|
||||
add(new SpliceInstruction(expressions.length, IntStreamEx.ofIndices(expressions).toArray()));
|
||||
GotoInstruction gotoInstruction = new GotoInstruction(null);
|
||||
gotoInstruction.setOffset(myAnalyzer.getInstructionCount());
|
||||
dup().push(factory.getConstFactory().getSentinel()).compare(JavaTokenType.EQEQ);
|
||||
ConditionalGotoInstruction condGoto = new ConditionalGotoInstruction(null, false, null);
|
||||
add(condGoto);
|
||||
assignTo(targetVariable);
|
||||
myBranches.add(() -> {
|
||||
add(gotoInstruction);
|
||||
condGoto.setOffset(myAnalyzer.getInstructionCount());
|
||||
pop();
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a synthetic variable (not declared in the original code) to be used within this control flow.
|
||||
*
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ import static com.intellij.psi.CommonClassNames.*;
|
||||
public class ControlFlowAnalyzer extends JavaElementVisitor {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.dataFlow.ControlFlowAnalyzer");
|
||||
public static final String ORG_JETBRAINS_ANNOTATIONS_CONTRACT = Contract.class.getName();
|
||||
private static final int MAX_UNROLL_SIZE = 3;
|
||||
static final int MAX_UNROLL_SIZE = 3;
|
||||
private final PsiElement myCodeFragment;
|
||||
private final boolean myIgnoreAssertions;
|
||||
private final boolean myInlining;
|
||||
|
||||
+6
-3
@@ -892,10 +892,13 @@ public class DfaMemoryStateImpl implements DfaMemoryState {
|
||||
return true;
|
||||
}
|
||||
|
||||
final boolean containsCalls = dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls();
|
||||
|
||||
if (dfaLeft == dfaRight) {
|
||||
return containsCalls || !isNegated;
|
||||
return !isNegated || (dfaLeft instanceof DfaVariableValue && ((DfaVariableValue)dfaLeft).containsCalls());
|
||||
}
|
||||
|
||||
DfaConstValue sentinel = getFactory().getConstFactory().getSentinel();
|
||||
if (dfaLeft == sentinel || dfaRight == sentinel) {
|
||||
return isNegated;
|
||||
}
|
||||
|
||||
if (isNull(dfaLeft) && isNotNull(dfaRight) || isNull(dfaRight) && isNotNull(dfaLeft)) {
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ abstract class ComparatorModel {
|
||||
|
||||
@Override
|
||||
void invoke(CFGBuilder builder) {
|
||||
builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().endIf();
|
||||
builder.dup().ifNotNull().chain(myDownstream::invoke).elseBranch().pop().end();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -74,7 +74,7 @@ public class MapUpdateInliner implements CallInliner {
|
||||
.push(builder.getFactory().createTypeValue(type, Nullness.NOT_NULL))
|
||||
.swap()
|
||||
.invokeFunction(2, function)
|
||||
.endIf()
|
||||
.end()
|
||||
.flushFields();
|
||||
return true;
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class MapUpdateInliner implements CallInliner {
|
||||
.elseBranch()
|
||||
.pop()
|
||||
.push(builder.getFactory().createTypeValue(type, Nullness.NOT_NULL))
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
|
||||
private static void inlineComputeIfPresent(@NotNull CFGBuilder builder,
|
||||
@@ -110,7 +110,7 @@ public class MapUpdateInliner implements CallInliner {
|
||||
.elseBranch()
|
||||
.pop()
|
||||
.pushNull()
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
|
||||
private static void inlineCompute(@NotNull CFGBuilder builder,
|
||||
|
||||
+10
-10
@@ -80,7 +80,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.splice(2, 0, 1, 1) // stack: .. elseValue, optValue, optValue
|
||||
.ifNotNull()
|
||||
.swap() // stack: .. optValue, elseValue
|
||||
.endIf()
|
||||
.end()
|
||||
.pop();
|
||||
})
|
||||
.register(OPTIONAL_OR_NULL, (builder, call) -> {
|
||||
@@ -94,7 +94,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.ifNull()
|
||||
.pop()
|
||||
.invokeFunction(0, fn)
|
||||
.endIf();
|
||||
.end();
|
||||
})
|
||||
.register(OPTIONAL_IF_PRESENT, (builder, call) -> {
|
||||
PsiExpression fn = call.getArgumentList().getExpressions()[0];
|
||||
@@ -106,7 +106,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.elseBranch()
|
||||
.pop()
|
||||
.pushUnknown()
|
||||
.endIf();
|
||||
.end();
|
||||
});
|
||||
|
||||
private static final CallMapper<BiConsumer<CFGBuilder, PsiExpression>> INTERMEDIATE_MAPPER =
|
||||
@@ -122,19 +122,19 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.ifConditionIs(false)
|
||||
.pop()
|
||||
.pushNull()
|
||||
.endIf()
|
||||
.endIf())
|
||||
.end()
|
||||
.end())
|
||||
.register(OPTIONAL_FLAT_MAP, (builder, function) -> builder
|
||||
.dup()
|
||||
.ifNotNull()
|
||||
.chain(b -> invokeAndUnwrapOptional(b, 1, function))
|
||||
.endIf())
|
||||
.end())
|
||||
.register(OPTIONAL_OR, (builder, function) -> builder
|
||||
.dup()
|
||||
.ifNull()
|
||||
.pop()
|
||||
.chain(b -> invokeAndUnwrapOptional(b, 0, function))
|
||||
.endIf())
|
||||
.end())
|
||||
.register(GUAVA_TO_JAVA, (builder, stub) -> {/* no op */});
|
||||
|
||||
@Override
|
||||
@@ -154,7 +154,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.push(factFactory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, true))
|
||||
.elseBranch()
|
||||
.push(factFactory.getFactValue(DfaFactType.OPTIONAL_PRESENCE, false))
|
||||
.endIf();
|
||||
.end();
|
||||
return true;
|
||||
}
|
||||
if (OPTIONAL_EMPTY.test(call)) {
|
||||
@@ -200,7 +200,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.push(builder.getFactory().createTypeValue(optionalElementType, Nullness.NOT_NULL))
|
||||
.elseBranch()
|
||||
.pushNull()
|
||||
.endIf()
|
||||
.end()
|
||||
.assignTo(builder.createTempVariable(optionalElementType));
|
||||
return true;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ public class OptionalChainInliner implements CallInliner {
|
||||
.dup()
|
||||
.ifNotNull()
|
||||
.invokeFunction(1, function, resultNullness)
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
|
||||
private static void inlineOf(CFGBuilder builder, PsiType optionalElementType, PsiMethodCallExpression qualifierCall) {
|
||||
|
||||
+46
-49
@@ -310,12 +310,12 @@ public class StreamChainInliner implements CallInliner {
|
||||
@Override
|
||||
void iteration(CFGBuilder builder) {
|
||||
builder.invokeFunction(1, myFunction)
|
||||
.ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName()))
|
||||
.pushForWrite(myResult)
|
||||
.push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName())))
|
||||
.assign()
|
||||
.pop()
|
||||
.endIf();
|
||||
.ifConditionIs(!"allMatch".equals(myCall.getMethodExpression().getReferenceName()))
|
||||
.pushForWrite(myResult)
|
||||
.push(builder.getFactory().getBoolean("anyMatch".equals(myCall.getMethodExpression().getReferenceName())))
|
||||
.assign()
|
||||
.pop()
|
||||
.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ public class StreamChainInliner implements CallInliner {
|
||||
.chain(myNext::iteration)
|
||||
.elseBranch()
|
||||
.pop()
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -415,13 +415,13 @@ public class StreamChainInliner implements CallInliner {
|
||||
} else {
|
||||
PsiType outType = StreamApiUtil.getStreamElementType(myCall.getType());
|
||||
builder.pop()
|
||||
.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.doWhile()
|
||||
.push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN))
|
||||
.chain(myNext::iteration)
|
||||
.endWhileUnknown()
|
||||
.endIf();
|
||||
.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.doWhileUnknown()
|
||||
.push(builder.getFactory().createTypeValue(outType, Nullness.UNKNOWN))
|
||||
.chain(myNext::iteration)
|
||||
.end()
|
||||
.end();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -467,7 +467,7 @@ public class StreamChainInliner implements CallInliner {
|
||||
.chain(myNext::iteration)
|
||||
.elseBranch()
|
||||
.pop()
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,7 +595,7 @@ public class StreamChainInliner implements CallInliner {
|
||||
.ifConditionIs(true)
|
||||
.push(builder.getFactory().getFactValue(DfaFactType.CAN_BE_NULL, false))
|
||||
.invokeFunction(2, myMerger)
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
// Actual addition of Map element is unnecessary for current analysis
|
||||
builder.pop();
|
||||
@@ -620,10 +620,10 @@ public class StreamChainInliner implements CallInliner {
|
||||
PsiExpression originalQualifier = firstStep.myCall.getMethodExpression().getQualifierExpression();
|
||||
if (originalQualifier == null) return false;
|
||||
builder.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.chain(b -> buildStreamCFG(b, firstStep, originalQualifier))
|
||||
.endIf()
|
||||
.push(builder.getFactory().createTypeValue(call.getType(), Nullness.NOT_NULL));
|
||||
.ifConditionIs(true)
|
||||
.chain(b -> buildStreamCFG(b, firstStep, originalQualifier))
|
||||
.end()
|
||||
.push(builder.getFactory().createTypeValue(call.getType(), Nullness.NOT_NULL));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -646,12 +646,11 @@ public class StreamChainInliner implements CallInliner {
|
||||
builder
|
||||
.evaluateFunction(fn)
|
||||
.chain(firstStep::before)
|
||||
.doWhile()
|
||||
.doWhileUnknown()
|
||||
.pushForWrite(builder.createTempVariable(inType))
|
||||
.invokeFunction(0, fn)
|
||||
.assign()
|
||||
.chain(firstStep::iteration)
|
||||
.endWhileUnknown();
|
||||
.chain(firstStep::iteration).end();
|
||||
return;
|
||||
}
|
||||
boolean empty = STREAM_EMPTY.test(sourceCall);
|
||||
@@ -669,10 +668,9 @@ public class StreamChainInliner implements CallInliner {
|
||||
}
|
||||
} else {
|
||||
builder
|
||||
.pushExpression(originalQualifier)
|
||||
.pop()
|
||||
.chain(firstStep::before)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType));
|
||||
.loopOver(args, builder.createTempVariable(inType))
|
||||
.chain(firstStep::iteration).end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -693,14 +691,14 @@ public class StreamChainInliner implements CallInliner {
|
||||
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
|
||||
if (qualifierValue != null) {
|
||||
builder.pushExpression(qualifierExpression)
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(qualifierExpression, NullabilityProblemKind.passingNullableToNotNullParameter)
|
||||
.pop()
|
||||
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getInt(0))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(qualifierExpression, NullabilityProblemKind.passingNullableToNotNullParameter)
|
||||
.pop()
|
||||
.push(SpecialField.ARRAY_LENGTH.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getInt(0))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -709,14 +707,14 @@ public class StreamChainInliner implements CallInliner {
|
||||
DfaValue qualifierValue = builder.getFactory().createValue(qualifierExpression);
|
||||
if (qualifierValue != null) {
|
||||
builder.pushExpression(qualifierExpression)
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(sourceCall, NullabilityProblemKind.callNPE)
|
||||
.pop()
|
||||
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getInt(0))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
.chain(firstStep::before)
|
||||
.checkNotNull(sourceCall, NullabilityProblemKind.callNPE)
|
||||
.pop()
|
||||
.push(SpecialField.COLLECTION_SIZE.createValue(builder.getFactory(), qualifierValue))
|
||||
.push(builder.getFactory().getInt(0))
|
||||
.ifCondition(JavaTokenType.GT)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -728,16 +726,15 @@ public class StreamChainInliner implements CallInliner {
|
||||
.pushUnknown()
|
||||
.ifConditionIs(true)
|
||||
.chain(b -> makeMainLoop(b, firstStep, inType))
|
||||
.endIf();
|
||||
.end();
|
||||
}
|
||||
|
||||
private static void makeMainLoop(CFGBuilder builder, Step firstStep, PsiType inType) {
|
||||
builder.doWhile()
|
||||
.pushForWrite(builder.createTempVariable(inType))
|
||||
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
|
||||
.assign()
|
||||
.chain(firstStep::iteration)
|
||||
.endWhileUnknown();
|
||||
builder.doWhileUnknown()
|
||||
.pushForWrite(builder.createTempVariable(inType))
|
||||
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
|
||||
.assign()
|
||||
.chain(firstStep::iteration).end();
|
||||
}
|
||||
|
||||
static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) {
|
||||
|
||||
+12
@@ -21,6 +21,7 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
@@ -31,11 +32,13 @@ import java.util.Map;
|
||||
|
||||
public class DfaConstValue extends DfaValue {
|
||||
private static final Throwable ourThrowable = new Throwable();
|
||||
private static final Object SENTINEL = ObjectUtils.sentinel("SENTINEL");
|
||||
public static class Factory {
|
||||
private final DfaConstValue dfaNull;
|
||||
private final DfaConstValue dfaFalse;
|
||||
private final DfaConstValue dfaTrue;
|
||||
private final DfaConstValue dfaFail;
|
||||
private final DfaConstValue dfaSentinel;
|
||||
private final DfaValueFactory myFactory;
|
||||
private final Map<Object, DfaConstValue> myValues = ContainerUtil.newHashMap();
|
||||
|
||||
@@ -45,6 +48,7 @@ public class DfaConstValue extends DfaValue {
|
||||
dfaFalse = new DfaConstValue(Boolean.FALSE, PsiType.BOOLEAN, factory, null);
|
||||
dfaTrue = new DfaConstValue(Boolean.TRUE, PsiType.BOOLEAN, factory, null);
|
||||
dfaFail = new DfaConstValue(ourThrowable, PsiType.VOID, factory, null);
|
||||
dfaSentinel = new DfaConstValue(SENTINEL, PsiType.VOID, factory, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -139,6 +143,14 @@ public class DfaConstValue extends DfaValue {
|
||||
public DfaConstValue getNull() {
|
||||
return dfaNull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sentinel value is special value used internally by dataflow. It cannot be stored to any variable, and equals to itself only
|
||||
* @return sentinel value
|
||||
*/
|
||||
public DfaConstValue getSentinel() {
|
||||
return dfaSentinel;
|
||||
}
|
||||
}
|
||||
|
||||
private final Object myValue;
|
||||
|
||||
@@ -42,6 +42,7 @@ public abstract class DfaValue {
|
||||
*/
|
||||
public DfaValue union(DfaValue other) {
|
||||
if(this == other) return this;
|
||||
if(this == DfaUnknownValue.getInstance() || other == DfaUnknownValue.getInstance()) return DfaUnknownValue.getInstance();
|
||||
return myFactory.getFactFactory().createValue(DfaFactMap.fromDfaValue(this).union(DfaFactMap.fromDfaValue(other)));
|
||||
}
|
||||
|
||||
|
||||
@@ -125,4 +125,22 @@ public class StreamKnownSource {
|
||||
System.out.println("Always");
|
||||
}
|
||||
}
|
||||
|
||||
void testLongVararg() {
|
||||
IntStream.of(1, 2, 3, 4, 5).anyMatch(x -> <warning descr="Condition 'x > 6' is always 'false'">x > 6</warning>);
|
||||
Stream.of("foo", "bar", "baz", "qux").filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
|
||||
}
|
||||
|
||||
void testShortVararg(String a, String b) {
|
||||
if(Stream.of(a, b).allMatch(Objects::isNull)) {
|
||||
if(<warning descr="Condition 'a != null' is always 'false'">a != null</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
if(<warning descr="Condition 'b != null' is always 'false'">b != null</warning>) {
|
||||
System.out.println("Impossible");
|
||||
}
|
||||
}
|
||||
IntStream.of(1, 2).anyMatch(x -> <warning descr="Condition 'x > 6' is always 'false'">x > 6</warning>);
|
||||
Stream.of("foo", "bar").filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,6 +350,14 @@ class OptionalGet {
|
||||
// Non-empty stream: get() is fine
|
||||
return Stream.of("foo", "bar", "baz").map(String::toUpperCase).max(Comparator.naturalOrder()).get();
|
||||
}
|
||||
|
||||
void testStreamOfUnrolling(Optional<String> optionalOne, Optional<String> optionalTwo, Optional<String> optionalThree) {
|
||||
if (Stream.of(optionalOne, optionalTwo).allMatch(Optional::isPresent)) {
|
||||
System.out.println(optionalOne.get());
|
||||
System.out.println(optionalTwo.get());
|
||||
System.out.println(optionalThree.<warning descr="'Optional.get()' without 'isPresent()' check">get</warning>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CtorTest {
|
||||
|
||||
Reference in New Issue
Block a user