Add call checker to report error more granulary if possible

This, however, works only for calls of 'synchronized' only. Thus, it
does not support inline functions of any kind.
This commit is contained in:
Ilmir Usmanov
2018-09-28 21:20:40 +03:00
parent 281f09f077
commit 9af7316845
21 changed files with 327 additions and 86 deletions
@@ -0,0 +1,69 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.jvm.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.isTopLevelInPackage
import org.jetbrains.kotlin.psi.KtCallExpression
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtValueArgumentList
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker
import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext
import org.jetbrains.kotlin.resolve.calls.checkers.findEnclosingSuspendFunction
import org.jetbrains.kotlin.resolve.calls.model.ExpressionValueArgument
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.kotlin.resolve.source.getPsi
class SuspensionPointInSynchronizedCallChecker : CallChecker {
override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) {
val descriptor = resolvedCall.candidateDescriptor
if (descriptor !is FunctionDescriptor || !descriptor.isSuspend) return
val enclosingSuspendFunctionSource = findEnclosingSuspendFunction(context)?.source?.getPsi() ?: return
// Search for `synchronized` call
var parent = reportOn
var child = reportOn
var insideLambda = false
while (parent != enclosingSuspendFunctionSource) {
if (parent is KtCallExpression) {
if (checkCall(context, parent, child, insideLambda, reportOn, resolvedCall)) break
}
if (parent is KtLambdaExpression) {
insideLambda = true
}
// The lambda is inside parentheses -> keep the child the same to check whether it is the second argument
if (parent !is KtValueArgumentList) {
child = parent
}
parent = parent.parent
}
}
private fun checkCall(
context: CallCheckerContext,
parent: KtCallExpression,
child: PsiElement,
insideLambda: Boolean,
reportOn: PsiElement,
resolvedCall: ResolvedCall<*>
): Boolean {
val call = context.trace[BindingContext.CALL, parent.calleeExpression] ?: return false
val resolved = context.trace[BindingContext.RESOLVED_CALL, call] ?: return false
val isSynchronized = resolved.resultingDescriptor.isTopLevelInPackage("synchronized", "kotlin")
if (isSynchronized) {
val isSecondArgument = (resolved.valueArgumentsByIndex?.get(1) as? ExpressionValueArgument)?.valueArgument == child
if (insideLambda && isSecondArgument) {
context.trace.report(ErrorsJvm.SUSPENSION_POINT_INSIDE_SYNCHRONIZED.on(reportOn, resolvedCall.resultingDescriptor))
}
return true
}
return false
}
}
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.jvm.diagnostics;
@@ -147,7 +136,8 @@ public class DefaultErrorMessagesJvm implements DefaultErrorMessages.Extension {
MAP.put(USAGE_OF_JVM_DEFAULT_THROUGH_SUPER_CALL, "Super calls of '@JvmDefault' members are only allowed with -Xjvm-default option");
MAP.put(NON_JVM_DEFAULT_OVERRIDES_JAVA_DEFAULT, "Non-@JvmDefault interface method cannot override default Java method. Please annotate this method with @JvmDefault");
MAP.put(EXPLICIT_METADATA_IS_DISALLOWED, "Explicit @Metadata is disallowed");
MAP.put(SUSPENSION_POINT_INSIDE_MONITOR, "A suspension point is inside a critical section");
MAP.put(SUSPENSION_POINT_INSIDE_MONITOR, "A suspension point at {0} is inside a critical section", STRING);
MAP.put(SUSPENSION_POINT_INSIDE_SYNCHRONIZED, "The ''{0}'' suspension point is inside a synchronized block", NAME);
}
@NotNull
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.jvm.diagnostics;
@@ -133,7 +122,8 @@ public interface ErrorsJvm {
DiagnosticFactory1<KtAnnotationEntry, String> ANNOTATION_TARGETS_NON_EXISTENT_ACCESSOR = DiagnosticFactory1.create(WARNING);
DiagnosticFactory0<PsiElement> SUSPENSION_POINT_INSIDE_MONITOR = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<PsiElement, String> SUSPENSION_POINT_INSIDE_MONITOR = DiagnosticFactory1.create(ERROR);
DiagnosticFactory1<PsiElement, CallableDescriptor> SUSPENSION_POINT_INSIDE_SYNCHRONIZED = DiagnosticFactory1.create(ERROR);
@SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() {
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.resolve.jvm.platform
@@ -52,6 +41,7 @@ object JvmPlatformConfigurator : PlatformConfigurator(
additionalCallCheckers = listOf(
JavaAnnotationCallChecker(),
SuspensionPointInSynchronizedCallChecker(),
JavaClassOnCompanionChecker(),
ProtectedInSuperClassCompanionCallChecker(),
UnsupportedSyntheticCallableReferenceChecker(),