Refine INSTANCE_ACCESS_BEFORE_SUPER_CALL check

- Detect usages of unitialized `this` as extension receiver argument
- Make it work within object literal created before super call

 #KT-9120 Fixed
 #KT-8289 Fixed
This commit is contained in:
Denis Zharkov
2015-12-17 16:49:11 +03:00
parent ba180f915a
commit 0d7c8635b3
17 changed files with 182 additions and 62 deletions
@@ -135,17 +135,22 @@ public class BodyResolver {
) {
ForceResolveUtil.forceResolveAllContents(descriptor.getAnnotations());
final CallChecker callChecker = new ConstructorHeaderCallChecker(descriptor);
resolveFunctionBody(outerDataFlowInfo, trace, constructor, descriptor, declaringScope,
new Function1<LexicalScope, DataFlowInfo>() {
@Override
public DataFlowInfo invoke(@NotNull LexicalScope headerInnerScope) {
return resolveSecondaryConstructorDelegationCall(outerDataFlowInfo, trace, headerInnerScope,
constructor, descriptor,
callChecker);
constructor, descriptor);
}
},
callChecker);
new Function1<LexicalScope, LexicalScope>() {
@Override
public LexicalScope invoke(LexicalScope scope) {
return new LexicalScopeImpl(
scope, descriptor, scope.isOwnerDescriptorAccessibleByLabel(), scope.getImplicitReceiver(),
LexicalScopeKind.CONSTRUCTOR_HEADER);
}
});
}
@Nullable
@@ -154,13 +159,11 @@ public class BodyResolver {
@NotNull BindingTrace trace,
@NotNull LexicalScope scope,
@NotNull KtSecondaryConstructor constructor,
@NotNull ConstructorDescriptor descriptor,
@NotNull CallChecker callChecker
@NotNull ConstructorDescriptor descriptor
) {
OverloadResolutionResults<?> results = callResolver.resolveConstructorDelegationCall(
trace, scope, outerDataFlowInfo,
descriptor, constructor.getDelegationCall(),
callChecker);
descriptor, constructor.getDelegationCall());
if (results != null && results.isSingleResult()) {
ResolvedCall<? extends CallableDescriptor> resolvedCall = results.getResultingCall();
@@ -775,27 +778,29 @@ public class BodyResolver {
) {
computeDeferredType(functionDescriptor.getReturnType());
resolveFunctionBody(outerDataFlowInfo, trace, function, functionDescriptor, declaringScope, null, CallChecker.DoNothing.INSTANCE$);
resolveFunctionBody(outerDataFlowInfo, trace, function, functionDescriptor, declaringScope, null, null);
assert functionDescriptor.getReturnType() != null;
}
public void resolveFunctionBody(
private void resolveFunctionBody(
@NotNull DataFlowInfo outerDataFlowInfo,
@NotNull BindingTrace trace,
@NotNull KtDeclarationWithBody function,
@NotNull FunctionDescriptor functionDescriptor,
@NotNull LexicalScope scope,
@Nullable Function1<LexicalScope, DataFlowInfo> beforeBlockBody,
@NotNull CallChecker callChecker
// Creates wrapper scope for header resolution if necessary (see resolveSecondaryConstructorBody)
@Nullable Function1<LexicalScope, LexicalScope> headerScopeFactory
) {
PreliminaryDeclarationVisitor.Companion.createForDeclaration(function, trace);
LexicalScope innerScope = FunctionDescriptorUtil.getFunctionInnerScope(scope, functionDescriptor, trace);
List<KtParameter> valueParameters = function.getValueParameters();
List<ValueParameterDescriptor> valueParameterDescriptors = functionDescriptor.getValueParameters();
LexicalScope headerScope = headerScopeFactory != null ? headerScopeFactory.invoke(innerScope) : innerScope;
valueParameterResolver.resolveValueParameters(
valueParameters, valueParameterDescriptors, innerScope, outerDataFlowInfo, trace, callChecker
valueParameters, valueParameterDescriptors, headerScope, outerDataFlowInfo, trace
);
// Synthetic "field" creation
@@ -823,7 +828,7 @@ public class BodyResolver {
DataFlowInfo dataFlowInfo = null;
if (beforeBlockBody != null) {
dataFlowInfo = beforeBlockBody.invoke(innerScope);
dataFlowInfo = beforeBlockBody.invoke(headerScope);
}
if (function.hasBody()) {
@@ -19,40 +19,40 @@ package org.jetbrains.kotlin.resolve
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.Errors
import org.jetbrains.kotlin.psi.KtConstructorDelegationCall
import org.jetbrains.kotlin.psi.KtInstanceExpressionWithLabel
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.context.BasicCallResolutionContext
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.resolvedCallUtil.hasImplicitThisOrSuperDispatchReceiver
import org.jetbrains.kotlin.resolve.descriptorUtil.getOwnerForEffectiveDispatchReceiverParameter
public class ConstructorHeaderCallChecker(constructor: ConstructorDescriptor) : CallChecker {
private val containingClass = constructor.getContainingDeclaration()
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
import org.jetbrains.kotlin.resolve.scopes.utils.parentsWithSelf
public object ConstructorHeaderCallChecker : CallChecker {
override fun <F : CallableDescriptor> check(resolvedCall: ResolvedCall<F>, context: BasicCallResolutionContext) {
if (resolvedCall.getStatus().isSuccess() &&
resolvedCall.hasImplicitThisOrSuperDispatchReceiver(context.trace.getBindingContext()) &&
containingClass == resolvedCall.getResultingDescriptor().getOwnerForEffectiveDispatchReceiverParameter()
) {
reportError(context, resolvedCall)
}
else {
val callElement = resolvedCall.getCall().getCallElement()
if (callElement is KtInstanceExpressionWithLabel) {
val descriptor = context.trace.get(BindingContext.REFERENCE_TARGET, callElement.getInstanceReference())
if (containingClass == descriptor) {
reportError(context, resolvedCall)
}
}
}
}
val dispatchReceiverClass = resolvedCall.dispatchReceiver.classDescriptorForImplicitReceiver
val extensionReceiverClass = resolvedCall.extensionReceiver.classDescriptorForImplicitReceiver
private fun reportError(context: BasicCallResolutionContext, resolvedCall: ResolvedCall<*>) {
context.trace.report(
Errors.INSTANCE_ACCESS_BEFORE_SUPER_CALL.on(context.call.getCalleeExpression(), resolvedCall.getResultingDescriptor()))
val labelReferenceClass =
(resolvedCall.call.callElement as? KtInstanceExpressionWithLabel)?.let {
instanceExpressionWithLabel ->
context.trace.get(BindingContext.REFERENCE_TARGET, instanceExpressionWithLabel.instanceReference) as? ClassDescriptor
}
if (dispatchReceiverClass == null && extensionReceiverClass == null && labelReferenceClass == null) return
if (context.scope.parentsWithSelf.any() {
it is LexicalScope && it.kind == LexicalScopeKind.CONSTRUCTOR_HEADER
&& (it.ownerDescriptor as ConstructorDescriptor).containingDeclaration in
setOf(dispatchReceiverClass, extensionReceiverClass, labelReferenceClass)
}) {
context.trace.report(
Errors.INSTANCE_ACCESS_BEFORE_SUPER_CALL.on(context.call.calleeExpression ?: return, resolvedCall.resultingDescriptor))
}
}
}
private val Receiver?.classDescriptorForImplicitReceiver: ClassDescriptor?
get() = (this as? ImplicitReceiver)?.declarationDescriptor as? ClassDescriptor
@@ -61,7 +61,8 @@ private val DEFAULT_DECLARATION_CHECKERS = listOf(
InfixModifierChecker())
private val DEFAULT_CALL_CHECKERS = listOf(CapturingInClosureChecker(), InlineCheckerWrapper(), ReifiedTypeParameterSubstitutionChecker(),
SafeCallChecker(), InvokeConventionChecker(), CallReturnsArrayOfNothingChecker())
SafeCallChecker(), InvokeConventionChecker(), CallReturnsArrayOfNothingChecker(),
ConstructorHeaderCallChecker)
private val DEFAULT_TYPE_CHECKERS = emptyList<AdditionalTypeChecker>()
private val DEFAULT_VALIDATORS = listOf(DeprecatedSymbolValidator(), OperatorValidator(), InfixValidator())
@@ -387,7 +387,7 @@ public class CallResolver {
public OverloadResolutionResults<FunctionDescriptor> resolveConstructorDelegationCall(
@NotNull BindingTrace trace, @NotNull LexicalScope scope, @NotNull DataFlowInfo dataFlowInfo,
@NotNull ConstructorDescriptor constructorDescriptor,
@NotNull KtConstructorDelegationCall call, @NotNull CallChecker callChecker
@NotNull KtConstructorDelegationCall call
) {
// Method returns `null` when there is nothing to resolve in trivial cases like `null` call expression or
// when super call should be conventional enum constructor and super call should be empty
@@ -397,7 +397,7 @@ public class CallResolver {
CallMaker.makeCall(null, null, call),
NO_EXPECTED_TYPE,
dataFlowInfo, ContextDependency.INDEPENDENT, CheckArgumentTypesMode.CHECK_VALUE_ARGUMENTS,
callChecker, false);
CallChecker.DoNothing.INSTANCE, false);
if (call.getCalleeExpression() == null) return checkArgumentTypesAndFail(context);
@@ -33,26 +33,18 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
// val x
// val y = other.x // return false for `other.x` as it's receiver is not `this`
// }
public fun ResolvedCall<*>.hasThisOrNoDispatchReceiver(context: BindingContext): Boolean =
hasThisOrNoDispatchReceiver(context, true, true)
public fun ResolvedCall<*>.hasImplicitThisOrSuperDispatchReceiver(context: BindingContext): Boolean =
hasThisOrNoDispatchReceiver(context, false, false)
private fun ResolvedCall<*>.hasThisOrNoDispatchReceiver(
context: BindingContext,
returnForNoReceiver: Boolean,
considerExplicitReceivers: Boolean
fun ResolvedCall<*>.hasThisOrNoDispatchReceiver(
context: BindingContext
): Boolean {
val dispatchReceiverValue = getDispatchReceiver()
if (getResultingDescriptor().getDispatchReceiverParameter() == null || dispatchReceiverValue == null) return returnForNoReceiver
val dispatchReceiverValue = dispatchReceiver
if (resultingDescriptor.dispatchReceiverParameter == null || dispatchReceiverValue == null) return true
var dispatchReceiverDescriptor: DeclarationDescriptor? = null
if (dispatchReceiverValue is ImplicitReceiver) {
// foo() -- implicit receiver
dispatchReceiverDescriptor = dispatchReceiverValue.declarationDescriptor
}
else if (dispatchReceiverValue is ExpressionReceiver && considerExplicitReceivers) {
else if (dispatchReceiverValue is ExpressionReceiver) {
val expression = KtPsiUtil.deparenthesize(dispatchReceiverValue.expression)
if (expression is KtThisExpression) {
// this.foo() -- explicit receiver
@@ -36,18 +36,16 @@ public class ValueParameterResolver(
private val constantExpressionEvaluator: ConstantExpressionEvaluator
) {
@JvmOverloads
public fun resolveValueParameters(
valueParameters: List<KtParameter>,
valueParameterDescriptors: List<ValueParameterDescriptor>,
declaringScope: LexicalScope,
dataFlowInfo: DataFlowInfo,
trace: BindingTrace,
callChecker: CallChecker = CallChecker.DoNothing
trace: BindingTrace
) {
val scopeForDefaultValue = LexicalScopeImpl(declaringScope, declaringScope.ownerDescriptor, false, null, LexicalScopeKind.DEFAULT_VALUE)
val contextForDefaultValue = ExpressionTypingContext.newContext(trace, scopeForDefaultValue, dataFlowInfo, TypeUtils.NO_EXPECTED_TYPE, callChecker)
val contextForDefaultValue = ExpressionTypingContext.newContext(trace, scopeForDefaultValue, dataFlowInfo, TypeUtils.NO_EXPECTED_TYPE, CallChecker.DoNothing)
for ((descriptor, parameter) in valueParameterDescriptors.zip(valueParameters)) {
ForceResolveUtil.forceResolveAllContents(descriptor.getAnnotations())
@@ -0,0 +1,15 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun A.foobar() = 3
class A {
fun foo() = 1
constructor(x: () -> Int)
constructor() : this(
{
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foo<!>() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this<!>.foo() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this@A<!>.foo() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foobar<!>()
})
}
@@ -0,0 +1,12 @@
package
public fun A.foobar(): kotlin.Int
public final class A {
public constructor A()
public constructor A(/*0*/ x: () -> kotlin.Int)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,12 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun A.foobar() = 3
class A {
fun foo() = 1
constructor(x: Any?)
constructor() : this(object {
fun bar() = <!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foo<!>() + <!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this@A<!>.foo() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foobar<!>() + <!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>super@A<!>.hashCode()
})
}
@@ -0,0 +1,12 @@
package
public fun A.foobar(): kotlin.Int
public final class A {
public constructor A()
public constructor A(/*0*/ x: kotlin.Any?)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,11 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun A.foobar() = 3
class A {
fun foo() = 1
constructor( x: Any = object {
fun bar() = <!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foo<!>() + <!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this@A<!>.foo() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foobar<!>()
})
}
@@ -0,0 +1,11 @@
package
public fun A.foobar(): kotlin.Int
public final class A {
public constructor A(/*0*/ x: kotlin.Any = ...)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public final fun foo(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -0,0 +1,15 @@
// !DIAGNOSTICS: -UNUSED_PARAMETER
fun A.foobar() = 1
val A.prop: Int get() = 2
class A {
constructor(x: Int)
constructor() : this(
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>foobar<!>() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this<!>.foobar() +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>prop<!> +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this<!>.prop +
<!INSTANCE_ACCESS_BEFORE_SUPER_CALL!>this@A<!>.prop
)
}
@@ -0,0 +1,12 @@
package
public val A.prop: kotlin.Int
public fun A.foobar(): kotlin.Int
public final class A {
public constructor A()
public constructor A(/*0*/ x: kotlin.Int)
public open override /*1*/ /*fake_override*/ fun equals(/*0*/ other: kotlin.Any?): kotlin.Boolean
public open override /*1*/ /*fake_override*/ fun hashCode(): kotlin.Int
public open override /*1*/ /*fake_override*/ fun toString(): kotlin.String
}
@@ -14831,12 +14831,30 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("lambdaAsArgument.kt")
public void testLambdaAsArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/lambdaAsArgument.kt");
doTest(fileName);
}
@TestMetadata("memberFunAccess.kt")
public void testMemberFunAccess() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/memberFunAccess.kt");
doTest(fileName);
}
@TestMetadata("objectLiteralAsArgument.kt")
public void testObjectLiteralAsArgument() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/objectLiteralAsArgument.kt");
doTest(fileName);
}
@TestMetadata("objectLiteralAsDefaultValueParameter.kt")
public void testObjectLiteralAsDefaultValueParameter() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/objectLiteralAsDefaultValueParameter.kt");
doTest(fileName);
}
@TestMetadata("passingInstance.kt")
public void testPassingInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/passingInstance.kt");
@@ -14873,6 +14891,12 @@ public class DiagnosticsTestGenerated extends AbstractDiagnosticsTest {
doTest(fileName);
}
@TestMetadata("thisAsExtensionReceiver.kt")
public void testThisAsExtensionReceiver() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/thisAsExtensionReceiver.kt");
doTest(fileName);
}
@TestMetadata("usingOuterInstance.kt")
public void testUsingOuterInstance() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/diagnostics/tests/secondaryConstructors/headerCallChecker/usingOuterInstance.kt");
@@ -1 +1 @@
<error>a</error> + b + c + a1
<error>a</error> + b + <error>c</error> + <error>a1</error>
@@ -1 +1 @@
<error>a</error> + b + c + a1
<error>a</error> + b + <error>c</error> + <error>a1</error>