[Wasm] Restrict types allowed in JS interop

- Prohibit Any, Array and other unsupported non-external types in JS
  interop context
- Add K1 diagnostic
- Update BE testdata

^KT-57136 Fixed
This commit is contained in:
Svyatoslav Kuzmich
2023-02-28 10:16:13 +01:00
committed by Space Team
parent bb05c8528f
commit 1e91fe155b
15 changed files with 299 additions and 121 deletions
@@ -29,6 +29,7 @@ object WasmPlatformConfigurator : PlatformConfiguratorBase(
WasmExternalDeclarationChecker,
WasmImportAnnotationChecker,
WasmJsFunAnnotationChecker,
WasmJsInteropTypesChecker,
),
additionalCallCheckers = listOf(
JsModuleCallChecker,
@@ -18,6 +18,13 @@ private val DIAGNOSTIC_FACTORY_TO_RENDERER by lazy {
Renderers.RENDER_TYPE
)
put(
ErrorsWasm.WRONG_JS_INTEROP_TYPE, "Type {1} cannot be used in {0}. " +
"Only external, primitive, string and function types are supported in Kotlin/Wasm JS interop.",
CommonRenderers.STRING,
Renderers.RENDER_TYPE
)
put(ErrorsWasm.NESTED_WASM_IMPORT, "Only top-level functions can be imported with @WasmImport")
put(ErrorsWasm.WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION, "Functions annotated with @WasmImport must be external")
put(ErrorsWasm.WASM_IMPORT_PARAMETER_DEFAULT_VALUE, "Default parameter values are not supported with @WasmImport")
@@ -6,10 +6,7 @@
package org.jetbrains.kotlin.wasm.resolve.diagnostics;
import com.intellij.psi.PsiElement;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0;
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.diagnostics.PositioningStrategies;
import org.jetbrains.kotlin.diagnostics.*;
import org.jetbrains.kotlin.psi.KtElement;
import org.jetbrains.kotlin.types.KotlinType;
@@ -19,6 +16,9 @@ public interface ErrorsWasm {
DiagnosticFactory1<KtElement, KotlinType> NON_EXTERNAL_TYPE_EXTENDS_EXTERNAL_TYPE =
DiagnosticFactory1.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory2<PsiElement, String, KotlinType>
WRONG_JS_INTEROP_TYPE = DiagnosticFactory2.create(ERROR, PositioningStrategies.DECLARATION_SIGNATURE_OR_DEFAULT);
DiagnosticFactory0<PsiElement> NESTED_WASM_IMPORT = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> WASM_IMPORT_ON_NON_EXTERNAL_DECLARATION = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> WASM_IMPORT_PARAMETER_DEFAULT_VALUE = DiagnosticFactory0.create(ERROR);
@@ -0,0 +1,152 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.wasm.resolve.diagnostics
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi
import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker
import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyExternal
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isNothing
import org.jetbrains.kotlin.types.typeUtil.isTypeParameter
import org.jetbrains.kotlin.types.typeUtil.isUnit
import org.jetbrains.kotlin.types.typeUtil.makeNotNullable
import org.jetbrains.kotlin.wasm.util.hasValidJsCodeBody
// TODO: Implement in K2: KT-56849
object WasmJsInteropTypesChecker : DeclarationChecker {
override fun check(declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext) {
if (descriptor !is MemberDescriptor)
return
val trace = context.trace
val bindingContext = trace.bindingContext
fun isExternalJsInteropDeclaration() =
descriptor.isEffectivelyExternal() &&
!descriptor.annotations.hasAnnotation(FqName("kotlin.wasm.WasmImport"))
fun isJsCodeDeclaration() =
(descriptor is FunctionDescriptor && descriptor.hasValidJsCodeBody(bindingContext) ||
descriptor is PropertyDescriptor && descriptor.hasValidJsCodeBody(bindingContext))
fun isJsExportDeclaration() =
AnnotationsUtils.isExportedObject(descriptor, bindingContext)
if (
!isExternalJsInteropDeclaration() &&
!isJsCodeDeclaration() &&
!isJsExportDeclaration()
) {
return
}
fun KotlinType.checkJsInteropType(
typePositionDescription: String,
reportOn: PsiElement,
isInFunctionReturnPosition: Boolean = false,
) {
if (!isTypeSupportedInJsInterop(this, isInFunctionReturnPosition)) {
trace.report(ErrorsWasm.WRONG_JS_INTEROP_TYPE.on(reportOn, typePositionDescription, this))
}
}
fun TypeParameterDescriptor.checkJsInteropTypeParameter() {
for (upperBound in this.upperBounds) {
if (!isTypeSupportedInJsInterop(upperBound, isInFunctionReturnPosition = false)) {
val reportOn = this.findPsi() ?: declaration
trace.report(ErrorsWasm.WRONG_JS_INTEROP_TYPE.on(reportOn, "type parameter upper bound", upperBound))
}
}
}
when (descriptor) {
is ClassDescriptor -> {
for (typeParameter in descriptor.declaredTypeParameters) {
typeParameter.checkJsInteropTypeParameter()
}
}
is PropertyDescriptor -> {
for (typeParameter in descriptor.typeParameters) {
typeParameter.checkJsInteropTypeParameter()
}
descriptor.type.checkJsInteropType("external property", declaration)
}
is FunctionDescriptor -> {
for (typeParameter in descriptor.typeParameters) {
typeParameter.checkJsInteropTypeParameter()
}
for (parameter in descriptor.valueParameters) {
val typeToCheck = parameter.varargElementType ?: parameter.type
typeToCheck.checkJsInteropType(
"external function parameter",
reportOn = parameter.findPsi() ?: declaration
)
}
descriptor.returnType?.checkJsInteropType(
"external function return",
reportOn = declaration,
isInFunctionReturnPosition = true
)
}
}
}
}
private fun isTypeSupportedInJsInterop(
type: KotlinType,
isInFunctionReturnPosition: Boolean,
): Boolean {
if (type.isUnit() || type.isNothing()) {
return isInFunctionReturnPosition
}
val nonNullable = type.makeNotNullable()
if (
KotlinBuiltIns.isPrimitiveType(nonNullable) ||
KotlinBuiltIns.isString(nonNullable)
) {
return true
}
// Interop type parameters upper bounds should are checked
// on declaration site separately
if (nonNullable.isTypeParameter()) {
return true
}
val classifierDescriptor = nonNullable.constructor.declarationDescriptor
if (classifierDescriptor is MemberDescriptor && classifierDescriptor.isEffectivelyExternal()) {
return true
}
if (type.isFunctionType) {
val arguments = type.arguments
for (i in 0 until arguments.lastIndex) {
val isArgumentSupported = isTypeSupportedInJsInterop(
arguments[i].type,
isInFunctionReturnPosition = false,
)
if (!isArgumentSupported) {
return false
}
}
return isTypeSupportedInJsInterop(
arguments.last().type, // Function type result type
isInFunctionReturnPosition = true,
)
}
return false
}