mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Better methodref support in DFA; Stream.generate() inlining support
This commit is contained in:
@@ -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$");
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -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,
|
||||
|
||||
+2
-1
@@ -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 ||
|
||||
|
||||
+6
-2
@@ -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 =
|
||||
|
||||
+33
-15
@@ -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) {
|
||||
|
||||
+3
-1
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -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
|
||||
|
||||
+17
-3
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user