KT-4405 Control-flow analysis is not performed for some local declarations #KT-4405 Fixed
This commit is contained in:
@@ -91,23 +91,29 @@ public class JetFlowInformationProvider {
|
|||||||
return pseudocodeVariablesData;
|
return pseudocodeVariablesData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void checkFunction(
|
public void checkForLocalClassOrObjectMode() {
|
||||||
@NotNull JetDeclarationWithBody function,
|
// Local classes and objects are analyzed twice: when TopDownAnalyzer processes it and as a part of its container.
|
||||||
@NotNull JetType expectedReturnType,
|
// Almost all checks can be done when the container is analyzed
|
||||||
boolean isLocalObject
|
// except recording initialized variables (this information is needed for DeclarationChecker).
|
||||||
) {
|
recordInitializedVariables();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkDeclaration() {
|
||||||
|
|
||||||
recordInitializedVariables();
|
recordInitializedVariables();
|
||||||
|
|
||||||
checkDefiniteReturn(expectedReturnType);
|
|
||||||
checkLocalFunctions();
|
checkLocalFunctions();
|
||||||
|
|
||||||
if (isLocalObject) return;
|
|
||||||
|
|
||||||
markUninitializedVariables();
|
markUninitializedVariables();
|
||||||
|
|
||||||
markUnusedVariables();
|
markUnusedVariables();
|
||||||
|
|
||||||
markUnusedLiteralsInBlock();
|
markUnusedLiteralsInBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkFunction(@Nullable JetType expectedReturnType) {
|
||||||
|
|
||||||
|
checkDefiniteReturn(expectedReturnType != null ? expectedReturnType : NO_EXPECTED_TYPE);
|
||||||
|
|
||||||
markTailCalls();
|
markTailCalls();
|
||||||
}
|
}
|
||||||
@@ -175,16 +181,17 @@ public class JetFlowInformationProvider {
|
|||||||
private void checkLocalFunctions() {
|
private void checkLocalFunctions() {
|
||||||
for (LocalFunctionDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) {
|
for (LocalFunctionDeclarationInstruction localDeclarationInstruction : pseudocode.getLocalDeclarations()) {
|
||||||
JetElement element = localDeclarationInstruction.getElement();
|
JetElement element = localDeclarationInstruction.getElement();
|
||||||
if (element instanceof JetNamedFunction) {
|
if (element instanceof JetDeclarationWithBody) {
|
||||||
JetNamedFunction localFunction = (JetNamedFunction) element;
|
JetDeclarationWithBody localDeclaration = (JetDeclarationWithBody) element;
|
||||||
SimpleFunctionDescriptor functionDescriptor = trace.getBindingContext().get(BindingContext.FUNCTION, localFunction);
|
if (localDeclaration instanceof JetFunctionLiteral) continue;
|
||||||
|
CallableDescriptor functionDescriptor =
|
||||||
|
(CallableDescriptor) trace.getBindingContext().get(BindingContext.DECLARATION_TO_DESCRIPTOR, localDeclaration);
|
||||||
JetType expectedType = functionDescriptor != null ? functionDescriptor.getReturnType() : null;
|
JetType expectedType = functionDescriptor != null ? functionDescriptor.getReturnType() : null;
|
||||||
|
|
||||||
JetFlowInformationProvider providerForLocalDeclaration =
|
JetFlowInformationProvider providerForLocalDeclaration =
|
||||||
new JetFlowInformationProvider(localFunction, trace, localDeclarationInstruction.getBody());
|
new JetFlowInformationProvider(localDeclaration, trace, localDeclarationInstruction.getBody());
|
||||||
|
|
||||||
providerForLocalDeclaration.checkDefiniteReturn(expectedType != null ? expectedType : NO_EXPECTED_TYPE);
|
providerForLocalDeclaration.checkFunction(expectedType);
|
||||||
providerForLocalDeclaration.markTailCalls();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
package org.jetbrains.jet.lang.resolve;
|
package org.jetbrains.jet.lang.resolve;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.jet.lang.cfg.JetFlowInformationProvider;
|
import org.jetbrains.jet.lang.cfg.JetFlowInformationProvider;
|
||||||
import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
|
import org.jetbrains.jet.lang.descriptors.PropertyAccessorDescriptor;
|
||||||
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
|
import org.jetbrains.jet.lang.descriptors.PropertyDescriptor;
|
||||||
@@ -59,8 +60,6 @@ public class ControlFlowAnalyzer {
|
|||||||
JetType expectedReturnType = !function.hasBlockBody() && !function.hasDeclaredReturnType()
|
JetType expectedReturnType = !function.hasBlockBody() && !function.hasDeclaredReturnType()
|
||||||
? NO_EXPECTED_TYPE
|
? NO_EXPECTED_TYPE
|
||||||
: functionDescriptor.getReturnType();
|
: functionDescriptor.getReturnType();
|
||||||
assert expectedReturnType != null
|
|
||||||
: "functionDescriptor is not yet fully initialized or broken so return type is null " + functionDescriptor;
|
|
||||||
checkFunction(function, expectedReturnType);
|
checkFunction(function, expectedReturnType);
|
||||||
}
|
}
|
||||||
for (Map.Entry<JetProperty, PropertyDescriptor> entry : bodiesResolveContext.getProperties().entrySet()) {
|
for (Map.Entry<JetProperty, PropertyDescriptor> entry : bodiesResolveContext.getProperties().entrySet()) {
|
||||||
@@ -75,11 +74,11 @@ public class ControlFlowAnalyzer {
|
|||||||
// A pseudocode of class/object initialization corresponds to a class/object
|
// A pseudocode of class/object initialization corresponds to a class/object
|
||||||
// or initialization of properties corresponds to a package declared in a file
|
// or initialization of properties corresponds to a package declared in a file
|
||||||
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider((JetElement) declarationContainer, trace);
|
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider((JetElement) declarationContainer, trace);
|
||||||
flowInformationProvider.recordInitializedVariables();
|
if (topDownAnalysisParameters.isDeclaredLocally()) {
|
||||||
|
flowInformationProvider.checkForLocalClassOrObjectMode();
|
||||||
if (topDownAnalysisParameters.isDeclaredLocally()) return;
|
return;
|
||||||
|
}
|
||||||
flowInformationProvider.markUninitializedVariables();
|
flowInformationProvider.checkDeclaration();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkProperty(JetProperty property, PropertyDescriptor propertyDescriptor) {
|
private void checkProperty(JetProperty property, PropertyDescriptor propertyDescriptor) {
|
||||||
@@ -89,15 +88,19 @@ public class ControlFlowAnalyzer {
|
|||||||
: propertyDescriptor.getSetter();
|
: propertyDescriptor.getSetter();
|
||||||
assert accessorDescriptor != null : "no property accessor descriptor " + accessor.getText();
|
assert accessorDescriptor != null : "no property accessor descriptor " + accessor.getText();
|
||||||
JetType returnType = accessorDescriptor.getReturnType();
|
JetType returnType = accessorDescriptor.getReturnType();
|
||||||
assert returnType != null : "property accessor has no return type " + accessorDescriptor;
|
|
||||||
checkFunction(accessor, returnType);
|
checkFunction(accessor, returnType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkFunction(@NotNull JetDeclarationWithBody function, @NotNull JetType expectedReturnType) {
|
private void checkFunction(@NotNull JetDeclarationWithBody function, @Nullable JetType expectedReturnType) {
|
||||||
JetExpression bodyExpression = function.getBodyExpression();
|
JetExpression bodyExpression = function.getBodyExpression();
|
||||||
if (bodyExpression == null) return;
|
if (bodyExpression == null) return;
|
||||||
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider(function, trace);
|
JetFlowInformationProvider flowInformationProvider = new JetFlowInformationProvider(function, trace);
|
||||||
flowInformationProvider.checkFunction(function, expectedReturnType, topDownAnalysisParameters.isDeclaredLocally());
|
if (topDownAnalysisParameters.isDeclaredLocally()) {
|
||||||
|
flowInformationProvider.checkForLocalClassOrObjectMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
flowInformationProvider.checkDeclaration();
|
||||||
|
flowInformationProvider.checkFunction(expectedReturnType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ class WithC() {
|
|||||||
{
|
{
|
||||||
$x = 1
|
$x = 1
|
||||||
<!UNRESOLVED_REFERENCE!>$y<!> = 2
|
<!UNRESOLVED_REFERENCE!>$y<!> = 2
|
||||||
val b = x
|
val <!UNUSED_VARIABLE!>b<!> = x
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val a : Int get() = 1
|
val a : Int get() = 1
|
||||||
|
|
||||||
{
|
{
|
||||||
val z = <!UNRESOLVED_REFERENCE!>b<!>
|
val <!UNUSED_VARIABLE!>z<!> = <!UNRESOLVED_REFERENCE!>b<!>
|
||||||
val zz = x
|
val <!UNUSED_VARIABLE!>zz<!> = x
|
||||||
val zzz = <!NO_BACKING_FIELD_CUSTOM_ACCESSORS!>$a<!>
|
val <!UNUSED_VARIABLE!>zzz<!> = <!NO_BACKING_FIELD_CUSTOM_ACCESSORS!>$a<!>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ class LocalValsVsProperties(val a: Int, w: Int) : Open(a, w) {
|
|||||||
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y : Int<!>
|
<!MUST_BE_INITIALIZED_OR_BE_ABSTRACT!>val y : Int<!>
|
||||||
{
|
{
|
||||||
$x = 1
|
$x = 1
|
||||||
val b = x
|
val <!UNUSED_VARIABLE!>b<!> = x
|
||||||
}
|
}
|
||||||
val b = a
|
val b = a
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class ReadForward() {
|
class ReadForward() {
|
||||||
{
|
{
|
||||||
val x = <!UNINITIALIZED_VARIABLE!>$a<!>
|
val <!UNUSED_VARIABLE!>x<!> = <!UNINITIALIZED_VARIABLE!>$a<!>
|
||||||
}
|
}
|
||||||
|
|
||||||
val a = 1
|
val a = 1
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
class ReadByAnotherPropertyInitializer() {
|
class ReadByAnotherPropertyInitializer() {
|
||||||
val a = 1
|
val a = 1
|
||||||
{
|
{
|
||||||
val x = $a
|
val <!UNUSED_VARIABLE!>x<!> = $a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -2,6 +2,6 @@ abstract class ReadNonexistent() {
|
|||||||
abstract val aa: Int
|
abstract val aa: Int
|
||||||
|
|
||||||
{
|
{
|
||||||
val x = <!NO_BACKING_FIELD_ABSTRACT_PROPERTY!>$aa<!>
|
val <!UNUSED_VARIABLE!>x<!> = <!NO_BACKING_FIELD_ABSTRACT_PROPERTY!>$aa<!>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -3,6 +3,6 @@ class ReadNonexistent() {
|
|||||||
get() = 1
|
get() = 1
|
||||||
|
|
||||||
{
|
{
|
||||||
val x = <!NO_BACKING_FIELD_CUSTOM_ACCESSORS!>$a<!>
|
val <!UNUSED_VARIABLE!>x<!> = <!NO_BACKING_FIELD_CUSTOM_ACCESSORS!>$a<!>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
class Cl() {
|
class Cl() {
|
||||||
{
|
{
|
||||||
val x = <!UNRESOLVED_REFERENCE!>$a<!>
|
val <!UNUSED_VARIABLE!>x<!> = <!UNRESOLVED_REFERENCE!>$a<!>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
//KT-4405 Control-flow analysis is not performed for some local declarations
|
||||||
|
|
||||||
|
package d
|
||||||
|
|
||||||
|
val closure = {
|
||||||
|
val <!UNUSED_VARIABLE!>x4<!> = "" // error: should be UNUSED_VARIABLE
|
||||||
|
|
||||||
|
fun g() {
|
||||||
|
val <!UNUSED_VARIABLE!>x6<!> = "" // error: should be UNUSED_VARIABLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun h(): Int { // error: should be NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY
|
||||||
|
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
|
||||||
|
}
|
||||||
|
|
||||||
|
class A {
|
||||||
|
{
|
||||||
|
fun foo(): Int {
|
||||||
|
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
|
||||||
|
|
||||||
|
val <!UNUSED_VARIABLE!>closure<!> = {
|
||||||
|
val <!UNUSED_VARIABLE!>x<!> = ""
|
||||||
|
|
||||||
|
fun local(): Int {
|
||||||
|
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
|
||||||
|
}
|
||||||
|
|
||||||
|
val <!UNUSED_VARIABLE!>y<!> = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ fun f() {
|
|||||||
|
|
||||||
fun loc2(): Int {
|
fun loc2(): Int {
|
||||||
val <!UNUSED_VARIABLE!>x2_<!> = "" // error: should be UNUSED_VARIABLE
|
val <!UNUSED_VARIABLE!>x2_<!> = "" // error: should be UNUSED_VARIABLE
|
||||||
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY, NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
|
<!NO_RETURN_IN_FUNCTION_WITH_BLOCK_BODY!>}<!>
|
||||||
}
|
}
|
||||||
|
|
||||||
val v: String
|
val v: String
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ package f
|
|||||||
|
|
||||||
object A {
|
object A {
|
||||||
class LoginFormPage() : Request({
|
class LoginFormPage() : Request({
|
||||||
val failed = session.get("LOGIN_FAILED")
|
val <!UNUSED_VARIABLE!>failed<!> = session.get("LOGIN_FAILED")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
class B {
|
class B {
|
||||||
class object {
|
class object {
|
||||||
class LoginFormPage() : Request({
|
class LoginFormPage() : Request({
|
||||||
val failed = session.get("LOGIN_FAILED")
|
val <!UNUSED_VARIABLE!>failed<!> = session.get("LOGIN_FAILED")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
class C {
|
class C {
|
||||||
class LoginFormPage() : Request({
|
class LoginFormPage() : Request({
|
||||||
val failed = session.get("LOGIN_FAILED")
|
val <!UNUSED_VARIABLE!>failed<!> = session.get("LOGIN_FAILED")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fun f() {
|
|||||||
val closure = {
|
val closure = {
|
||||||
class MyClass {
|
class MyClass {
|
||||||
{
|
{
|
||||||
val x: MyClass = MyClass()
|
val <!UNUSED_VARIABLE!>x<!>: MyClass = MyClass()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class A(a: String?) {
|
|||||||
val b = if (a != null) <!DEBUG_INFO_AUTOCAST!>a<!>.length else 1
|
val b = if (a != null) <!DEBUG_INFO_AUTOCAST!>a<!>.length else 1
|
||||||
{
|
{
|
||||||
if (a != null) {
|
if (a != null) {
|
||||||
val c = <!DEBUG_INFO_AUTOCAST!>a<!>.length
|
val <!UNUSED_VARIABLE!>c<!> = <!DEBUG_INFO_AUTOCAST!>a<!>.length
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import javax.swing.JFrame
|
|||||||
|
|
||||||
class KFrame() : JFrame() {
|
class KFrame() : JFrame() {
|
||||||
{
|
{
|
||||||
val x = this.rootPaneCheckingEnabled // make sure field is visible
|
val <!UNUSED_VARIABLE!>x<!> = this.rootPaneCheckingEnabled // make sure field is visible
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class A(vararg t : Int) {
|
class A(vararg t : Int) {
|
||||||
{
|
{
|
||||||
val t1 : IntArray = t
|
val <!UNUSED_VARIABLE!>t1<!> : IntArray = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class A(vararg val t : Int) {
|
class A(vararg val t : Int) {
|
||||||
{
|
{
|
||||||
val t1 : IntArray = t
|
val <!UNUSED_VARIABLE!>t1<!> : IntArray = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1618,6 +1618,11 @@ public class JetDiagnosticsTestGenerated extends AbstractJetDiagnosticsTest {
|
|||||||
doTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/kt3501.kt");
|
doTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/kt3501.kt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt4405.kt")
|
||||||
|
public void testKt4405() throws Exception {
|
||||||
|
doTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/kt4405.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt510.kt")
|
@TestMetadata("kt510.kt")
|
||||||
public void testKt510() throws Exception {
|
public void testKt510() throws Exception {
|
||||||
doTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/kt510.kt");
|
doTest("compiler/testData/diagnostics/tests/controlFlowAnalysis/kt510.kt");
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ class WithC() {
|
|||||||
{
|
{
|
||||||
$x = 1
|
$x = 1
|
||||||
<error>$y</error> = 2
|
<error>$y</error> = 2
|
||||||
val b = x
|
val <warning>b</warning> = x
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val a : Int get() = 1
|
val a : Int get() = 1
|
||||||
|
|
||||||
{
|
{
|
||||||
val z = <error>b</error>
|
val <warning>z</warning> = <error>b</error>
|
||||||
val zz = x
|
val <warning>zz</warning> = x
|
||||||
val zzz = <error>$a</error>
|
val <warning>zzz</warning> = <error>$a</error>
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
bar = 1
|
bar = 1
|
||||||
this.bar
|
this.bar
|
||||||
1 : Int
|
1 : Int
|
||||||
val a : Int =1
|
val <warning>a</warning> : Int =1
|
||||||
this : Foo
|
this : Foo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user