Prevent local value leaks via non-trivial field access

IDEA-230097 Inspection incorrectly shows "The call to 'assert true' always fails" on a Junit callback test

GitOrigin-RevId: 5ae966d139391f72c7d015297b093523dba5e5e8
This commit is contained in:
Tagir Valeev
2020-01-20 14:52:19 +07:00
committed by intellij-monorepo-bot
parent 90ca9a6360
commit 099d5fcf7c
3 changed files with 62 additions and 3 deletions

View File

@@ -466,14 +466,34 @@ public class StandardInstructionVisitor extends InstructionVisitor {
} }
if (!(value.getType() instanceof PsiArrayType) && if (!(value.getType() instanceof PsiArrayType) &&
(TypeConstraint.fromDfType(dfType).isComparedByEquals() || (TypeConstraint.fromDfType(dfType).isComparedByEquals() ||
instruction.shouldFlushFields() || !(instruction.getResultType() instanceof PsiPrimitiveType))) { instruction.shouldFlushFields() || mayLeakFromType(instruction.getResultType()))) {
// For now drop locality on every qualified call except primitive returning pure calls
// as value might escape through the return value
value = dropLocality(value, memState); value = dropLocality(value, memState);
} }
return value; return value;
} }
private boolean mayLeakFromType(PsiType type) {
// Complex value from field or method return call may contain back-reference to the object, so
// local value could leak. Do not drop locality only for some simple values.
if (type == null) return true;
type = type.getDeepComponentType();
return !(type instanceof PsiPrimitiveType) && !TypeUtils.isJavaLangString(type);
}
@Override
public DfaInstructionState[] visitPush(ExpressionPushingInstruction<?> instruction,
DataFlowRunner runner,
DfaMemoryState memState,
DfaValue value) {
if (value instanceof DfaVariableValue && mayLeakFromType(value.getType())) {
DfaVariableValue qualifier = ((DfaVariableValue)value).getQualifier();
if (qualifier != null) {
dropLocality(qualifier, memState);
}
}
return super.visitPush(instruction, runner, memState, value);
}
private Set<DfaCallState> addContractResults(MethodContract contract, private Set<DfaCallState> addContractResults(MethodContract contract,
Set<DfaCallState> states, Set<DfaCallState> states,
DfaValueFactory factory, DfaValueFactory factory,

View File

@@ -0,0 +1,38 @@
// IDEA-230097
class EscapeAnalysisLambdaInConstructor {
public void incorrectInspection() {
Callbackable callbackable = new Callbackable();
EventListenerSetup listener = new EventListenerSetup();
callbackable.setCallback(listener.listener);
if (listener.listenerHasBeenCalled) {
throw new AssertionError();
}
callbackable.callCallback();
if (!listener.listenerHasBeenCalled) {
throw new AssertionError();
}
}
}
class Callbackable {
Runnable callback;
public void setCallback(Runnable callback) {
this.callback = callback;
}
public void callCallback(){
callback.run();
}
}
class EventListenerSetup {
final Runnable listener;
boolean listenerHasBeenCalled = false;
public EventListenerSetup() {
this.listener = () -> listenerHasBeenCalled = true;
}
}

View File

@@ -222,6 +222,7 @@ public class DataFlowInspection8Test extends DataFlowInspectionTestCase {
public void testMethodReferenceBoundToNullable() { doTestWithCustomAnnotations(); } public void testMethodReferenceBoundToNullable() { doTestWithCustomAnnotations(); }
public void testEscapeAnalysis() { doTest(); } public void testEscapeAnalysis() { doTest(); }
public void testEscapeAnalysisLambdaInConstructor() { doTest(); }
public void testThisAsVariable() { doTest(); } public void testThisAsVariable() { doTest(); }
public void testQueuePeek() { doTest(); } public void testQueuePeek() { doTest(); }
public void testForeachCollectionElement() { doTest(); } public void testForeachCollectionElement() { doTest(); }