Better methodref support in DFA; Stream.generate() inlining support

This commit is contained in:
Tagir Valeev
2017-08-11 15:50:58 +07:00
parent bb4d6e7236
commit 130a641011
11 changed files with 98 additions and 35 deletions
@@ -449,12 +449,10 @@ public class CFGBuilder {
PsiVariable qualifierBinding = createTempVariable(qualifier.getType());
pushVariable(qualifierBinding)
.pushExpression(qualifier)
.dup();
myAnalyzer.addInstruction(new FieldReferenceInstruction(qualifier, ControlFlowAnalyzer.METHOD_REFERENCE_QUALIFIER_SYNTHETIC_FIELD));
assign().pop();
.checkNotNull(qualifier, NullabilityProblem.fieldAccessNPE)
.assign()
.pop();
myMethodRefQualifiers.put(methodRef, qualifierBinding);
} else {
pushExpression(methodRef).pop();
}
return this;
}
@@ -599,4 +597,8 @@ public class CFGBuilder {
operation.accept(this);
return this;
}
public static boolean isTempVariable(PsiModifierListOwner variable) {
return variable instanceof LightVariableBuilder && ((LightVariableBuilder)variable).getName().startsWith("tmp$");
}
}
@@ -15,10 +15,13 @@
*/
package com.intellij.codeInspection.dataFlow;
import com.intellij.codeInspection.dataFlow.instructions.MethodCallInstruction;
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.codeInspection.dataFlow.value.DfaRelationValue.RelationType;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiMethodReferenceExpression;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ObjectUtils;
import com.siyeh.ig.callMatcher.CallMapper;
@@ -67,8 +70,14 @@ public class CustomMethodHandlers {
.register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
(args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true));
public static CustomMethodHandler find(PsiMethodCallExpression call) {
return CUSTOM_METHOD_HANDLERS.mapFirst(call);
public static CustomMethodHandler find(MethodCallInstruction instruction) {
PsiElement context = instruction.getContext();
if(context instanceof PsiMethodCallExpression) {
return CUSTOM_METHOD_HANDLERS.mapFirst((PsiMethodCallExpression)context);
} else if(context instanceof PsiMethodReferenceExpression) {
return CUSTOM_METHOD_HANDLERS.mapFirst((PsiMethodReferenceExpression)context);
}
return null;
}
private static List<DfaMemoryState> stringStartsEnds(DfaCallArguments args,
@@ -1043,7 +1043,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
protected void processMethodReferenceResult(PsiMethodReferenceExpression methodRef,
List<? extends MethodContract> contracts,
DfaValue res) {
if(contracts.stream().anyMatch(c -> !c.isTrivial())) {
if(contracts.isEmpty() || !contracts.get(0).isTrivial()) {
// Do not track if method reference may have different results
myMethodReferenceResults.merge(methodRef, res, (a, b) -> a == b ? a : DfaUnknownValue.getInstance());
}
@@ -1056,6 +1056,7 @@ public class DataFlowInspectionBase extends BaseJavaBatchLocalInspectionTool {
}
private static boolean hasNonTrivialBooleanContracts(MethodCallInstruction instruction) {
if (CustomMethodHandlers.find(instruction) != null) return true;
List<MethodContract> contracts = instruction.getContracts();
return !contracts.isEmpty() && contracts.stream().anyMatch(
contract -> (contract.getReturnValue() == MethodContract.ValueConstraint.FALSE_VALUE ||
@@ -320,12 +320,17 @@ public class StandardInstructionVisitor extends InstructionVisitor {
}
}
PsiMethodReferenceExpression methodRef = instruction.getMethodType() == MethodCallInstruction.MethodType.METHOD_REFERENCE_CALL ?
(PsiMethodReferenceExpression)instruction.getContext() : null;
DfaInstructionState[] result = new DfaInstructionState[finalStates.size()];
int i = 0;
for (DfaMemoryState state : finalStates) {
if (instruction.shouldFlushFields()) {
state.flushFields();
}
if (methodRef != null) {
processMethodReferenceResult(methodRef, instruction.getContracts(), state.peek());
}
result[i++] = new DfaInstructionState(runner.getInstruction(instruction.getIndex() + 1), state);
}
return result;
@@ -333,8 +338,7 @@ public class StandardInstructionVisitor extends InstructionVisitor {
@NotNull
private List<DfaMemoryState> handleKnownMethods(MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) {
PsiMethodCallExpression call = ObjectUtils.tryCast(instruction.getCallExpression(), PsiMethodCallExpression.class);
CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(call);
CustomMethodHandlers.CustomMethodHandler handler = CustomMethodHandlers.find(instruction);
if (handler == null) return Collections.emptyList();
DfaCallArguments callArguments = popCall(instruction, runner, memState, false);
List<DfaMemoryState> states =
@@ -31,10 +31,8 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.UnaryOperator;
import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM;
import static com.intellij.psi.CommonClassNames.JAVA_UTIL_STREAM_STREAM;
import static com.siyeh.ig.callMatcher.CallMatcher.anyOf;
import static com.siyeh.ig.callMatcher.CallMatcher.instanceCall;
import static com.intellij.psi.CommonClassNames.*;
import static com.siyeh.ig.callMatcher.CallMatcher.*;
public class StreamChainInliner implements CallInliner {
private static final String[] TERMINALS =
@@ -58,6 +56,12 @@ public class StreamChainInliner implements CallInliner {
instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "flatMap", "flatMapToInt", "flatMapToLong", "flatMapToDouble").parameterCount(1);
private static final CallMatcher PEEK = instanceCall(JAVA_UTIL_STREAM_BASE_STREAM, "peek").parameterCount(1);
private static final CallMatcher STREAM_GENERATE = anyOf(
staticCall(JAVA_UTIL_STREAM_STREAM, "generate").parameterCount(1),
staticCall(JAVA_UTIL_STREAM_INT_STREAM, "generate").parameterCount(1),
staticCall(JAVA_UTIL_STREAM_LONG_STREAM, "generate").parameterCount(1),
staticCall(JAVA_UTIL_STREAM_DOUBLE_STREAM, "generate").parameterCount(1));
private static final CallMapper<UnaryOperator<Step>> INTERMEDIATE_STEP_MAPPER = new CallMapper<UnaryOperator<Step>>()
.register(FILTER, (PsiMethodCallExpression call) -> (Step next) -> new FilterStep(call, next))
.register(MAP, (PsiMethodCallExpression call) -> (Step next) -> new MapStep(call, next))
@@ -310,17 +314,31 @@ public class StreamChainInliner implements CallInliner {
static void buildStreamCFG(CFGBuilder builder, Step firstStep, PsiExpression originalQualifier) {
PsiType inType = StreamApiUtil.getStreamElementType(originalQualifier.getType());
builder
.pushExpression(originalQualifier)
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
.pop()
.chain(firstStep::before)
.doWhile()
.pushVariable(builder.createTempVariable(inType))
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
PsiMethodCallExpression sourceCall = ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(originalQualifier), PsiMethodCallExpression.class);
if(STREAM_GENERATE.test(sourceCall)) {
PsiExpression fn = sourceCall.getArgumentList().getExpressions()[0];
builder
.evaluateFunction(fn)
.chain(firstStep::before)
.doWhile()
.pushVariable(builder.createTempVariable(inType))
.invokeFunction(0, fn)
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
} else {
builder
.pushExpression(originalQualifier)
.checkNotNull(firstStep.myCall, NullabilityProblem.callNPE)
.pop()
.chain(firstStep::before)
.doWhile()
.pushVariable(builder.createTempVariable(inType))
.push(builder.getFactory().createTypeValue(inType, DfaPsiUtil.getTypeNullability(inType)))
.assign()
.chain(firstStep::iteration)
.endWhileUnknown();
}
}
static Step buildChain(PsiMethodCallExpression qualifierCall, Step terminalStep) {
@@ -157,7 +157,9 @@ public class DfaVariableValue extends DfaValue {
}
public boolean isFlushableByCalls() {
if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter) return false;
if (myVariable instanceof PsiLocalVariable || myVariable instanceof PsiParameter || CFGBuilder.isTempVariable(myVariable)) {
return false;
}
boolean finalField = myVariable instanceof PsiVariable && myVariable.hasModifierProperty(PsiModifier.FINAL);
boolean specialFinalField = myVariable instanceof PsiMethod &&
Arrays.stream(SpecialField.values()).anyMatch(sf -> sf.isFinal() && sf.isMyAccessor(myVariable));
@@ -153,8 +153,8 @@ public class LongRangeKnownMethods {
void testStringComparison(String name) {
// Parentheses misplaced -- found in AndroidStudio
if (!(name.equals("layout_width") && <warning descr="Condition '!(name.equals(\"layout_height\"))' is always 'true'">!(name.equals("layout_height"))</warning> &&
<warning descr="Condition '!(name.equals(\"id\"))' is always 'true'">!(name.equals("id"))</warning>)) {
if (!(name.equals("layout_width") && <warning descr="Condition '!(name.equals(\"layout_height\"))' is always 'true'">!(<warning descr="Condition 'name.equals(\"layout_height\")' is always 'false'">name.equals("layout_height")</warning>)</warning> &&
<warning descr="Condition '!(name.equals(\"id\"))' is always 'true'">!(<warning descr="Condition 'name.equals(\"id\")' is always 'false'">name.equals("id")</warning>)</warning>)) {
System.out.println("ok");
}
}
@@ -12,7 +12,7 @@ public class OptionalInlining {
System.out.println("Always");
}
String s3 = Optional.of(Math.random() > 0.5 ? "foo" : "baz").orElse("bar");
if (<warning descr="Condition 's3.equals(\"foo\") || s3.equals(\"baz\")' is always 'true'">s3.equals("foo") || s3.equals("baz")</warning>) {
if (<warning descr="Condition 's3.equals(\"foo\") || s3.equals(\"baz\")' is always 'true'">s3.equals("foo") || <warning descr="Condition 's3.equals(\"baz\")' is always 'true' when reached">s3.equals("baz")</warning></warning>) {
System.out.println("Always");
}
if (<warning descr="Condition 's3.equals(\"bar\")' is always 'false'">s3.equals("bar")</warning>) {
@@ -111,7 +111,7 @@ public class OptionalInlining {
void testMap(Optional<String> opt) {
opt.map(<warning descr="Passing 'null' argument to parameter annotated as @NotNull">null</warning>);
String res = opt.<String>map(s -> null).orElse("abc");
if (<warning descr="Condition '!res.equals(\"abc\")' is always 'false'">!res.equals("abc")</warning>) {
if (<warning descr="Condition '!res.equals(\"abc\")' is always 'false'">!<warning descr="Condition 'res.equals(\"abc\")' is always 'true'">res.equals("abc")</warning></warning>) {
System.out.println("Never");
}
String trimmed = Optional.ofNullable(nullableMethod()).map(xx -> xx.trim()).orElse("");
@@ -2,8 +2,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import java.util.stream.*;
public class StreamInlining {
void testNulls(List<String> list) {
@@ -26,6 +25,7 @@ public class StreamInlining {
if(list.stream().filter(Objects::nonNull).anyMatch(x -> <warning descr="Condition 'x == null' is always 'false'">x == null</warning>)) {
System.out.println("never");
}
list.stream().map(Integer::valueOf).filter(<warning descr="Method reference result is always 'true'">Objects::nonNull</warning>).forEach(System.out::println);
}
void filterDistinctLimitSkipFilter(List<String> list) {
@@ -118,4 +118,12 @@ public class StreamInlining {
Optional<String> testOptionalOfNullable(List<String> list) {
return list.stream().filter(Objects::isNull).map(Optional::<warning descr="Passing 'null' argument to 'Optional'">ofNullable</warning>).findFirst().orElse(Optional.empty());
}
void testGenerate() {
List<String> list1 = Stream.generate(() -> Math.random() > 0.5 ? "foo" : "baz")
.limit(10).filter((xyz -> <warning descr="Condition '\"bar\".equals(xyz)' is always 'false'">"bar".equals(xyz)</warning>)).collect(Collectors.toList());
List<String> list2 = Stream.generate(() -> "xyz").limit(20).filter(<warning descr="Method reference result is always 'false'">"bar"::equals</warning>).collect(Collectors.toList());
Stream.generate(() -> Optional.of("xyz")).filter(<warning descr="Method reference result is always 'true'">Optional::isPresent</warning>).forEach(System.out::println);
LongStream.generate(() -> 5).limit(10).filter(x -> <warning descr="Condition 'x > 6' is always 'false'">x > 6</warning>).forEach(s -> System.out.println(s));
}
}
@@ -16,6 +16,7 @@
package com.siyeh.ig.callMatcher;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiMethodReferenceExpression;
import java.util.function.Function;
@@ -46,6 +47,10 @@ public class CallHandler<T> implements Function<PsiMethodCallExpression, T> {
return matcher().test(call) ? myTransformer.apply(call) : null;
}
public T applyMethodReference(PsiMethodReferenceExpression ref) {
return matcher().methodReferenceMatches(ref) ? myTransformer.apply(null) : null;
}
/**
* Creates a new CallHandler with specific matcher and specific transformer function
* @param matcher a matcher to be applied to the elements
@@ -16,6 +16,7 @@
package com.siyeh.ig.callMatcher;
import com.intellij.psi.PsiMethodCallExpression;
import com.intellij.psi.PsiMethodReferenceExpression;
import one.util.streamex.StreamEx;
import java.util.ArrayList;
@@ -31,7 +32,7 @@ import java.util.stream.Stream;
* @author Tagir Valeev
*/
public class CallMapper<T> {
private Map<String, List<Function<PsiMethodCallExpression, T>>> myMap = new HashMap<>();
private Map<String, List<CallHandler<T>>> myMap = new HashMap<>();
public CallMapper() {}
@@ -61,7 +62,7 @@ public class CallMapper<T> {
public T mapFirst(PsiMethodCallExpression call) {
if (call == null) return null;
List<Function<PsiMethodCallExpression, T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
List<CallHandler<T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
if (functions == null) return null;
for (Function<PsiMethodCallExpression, T> function : functions) {
T t = function.apply(call);
@@ -72,9 +73,22 @@ public class CallMapper<T> {
return null;
}
public T mapFirst(PsiMethodReferenceExpression methodRef) {
if (methodRef == null) return null;
List<CallHandler<T>> functions = myMap.get(methodRef.getReferenceName());
if (functions == null) return null;
for (CallHandler<T> function : functions) {
T t = function.applyMethodReference(methodRef);
if (t != null) {
return t;
}
}
return null;
}
public Stream<T> mapAll(PsiMethodCallExpression call) {
if (call == null) return null;
List<Function<PsiMethodCallExpression, T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
List<CallHandler<T>> functions = myMap.get(call.getMethodExpression().getReferenceName());
if (functions == null) return StreamEx.empty();
return StreamEx.of(functions).map(fn -> fn.apply(call)).nonNull();
}