From 629e0628d611436f8a3d8ad62559ef9c9f36ceae Mon Sep 17 00:00:00 2001 From: Alexander Korepanov Date: Thu, 21 Sep 2023 20:59:17 +0200 Subject: [PATCH] [JS IR] Implemented a set of JS code checks before klib serialization The diagnostics cannot be implemented with the FIR frontend checker because it requires constant evaluation over FIR. Therefore, the diagnostics are implemented as a set of klib checks over IR. For the diagnostics, the js() call argument must be evaluated and inlined as IrConst into IR in the same way as const val initializers and annotation arguments. ^KT-59388 Fixed ^KT-59399 Fixed ^KT-62425 Fixed --- compiler/ir/ir.interpreter/build.gradle.kts | 2 + .../IrConstExpressionTransformer.kt | 8 +- .../IrConstOnlyNecessaryTransformer.kt | 18 +++-- .../transformer/IrConstTransformer.kt | 2 +- .../ir/backend/js/checkers/JsKlibCheckers.kt | 55 +++++++++++-- .../js/checkers/JsKlibDeclarationsChecker.kt | 2 +- .../js/checkers/JsKlibDiagnosticContext.kt | 66 +++++++++++++++ .../ir/backend/js/checkers/JsKlibErrors.kt | 27 +++++++ .../js/checkers/JsKlibExpressionChecker.kt | 16 ++++ .../JsKlibEsModuleExportsChecker.kt | 13 +-- .../JsKlibOtherModuleExportsChecker.kt | 12 ++- .../expressions/JsKlibJsCodeCallChecker.kt | 80 +++++++++++++++++++ .../jetbrains/kotlin/ir/backend/js/klib.kt | 2 +- 13 files changed, 275 insertions(+), 28 deletions(-) create mode 100644 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt create mode 100644 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt create mode 100644 compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt diff --git a/compiler/ir/ir.interpreter/build.gradle.kts b/compiler/ir/ir.interpreter/build.gradle.kts index f4bbabce948..cad74271951 100644 --- a/compiler/ir/ir.interpreter/build.gradle.kts +++ b/compiler/ir/ir.interpreter/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { compileOnly(project(":compiler:ir.tree")) compileOnly(commonDependency("org.jetbrains.kotlin:kotlin-reflect")) { isTransitive = false } + + implementation(project(":core:compiler.common.js")) } optInToUnsafeDuringIrConstructionAPI() diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt index 7a9b336733a..5e06fd4cb1b 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstExpressionTransformer.kt @@ -42,14 +42,14 @@ internal abstract class IrConstExpressionTransformer( override fun visitClass(declaration: IrClass, data: Data): IrStatement { if (declaration.kind == ClassKind.ANNOTATION_CLASS) { - return super.visitClass(declaration, data.copy(inAnnotation = true)) + return super.visitClass(declaration, data.copy(inConstantExpression = true)) } return super.visitClass(declaration, data) } override fun visitCall(expression: IrCall, data: Data): IrElement { if (expression.canBeInterpreted()) { - return expression.interpret(failAsError = data.inAnnotation) + return expression.interpret(failAsError = data.inConstantExpression) } return super.visitCall(expression, data) } @@ -68,7 +68,7 @@ internal abstract class IrConstExpressionTransformer( override fun visitGetField(expression: IrGetField, data: Data): IrExpression { if (expression.canBeInterpreted()) { - return expression.interpret(failAsError = data.inAnnotation) + return expression.interpret(failAsError = data.inConstantExpression) } return super.visitGetField(expression, data) } @@ -78,7 +78,7 @@ internal abstract class IrConstExpressionTransformer( this.startOffset, this.endOffset, expression.type, listOf(this@wrapInStringConcat) ) - fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inAnnotation) + fun IrExpression.wrapInToStringConcatAndInterpret(): IrExpression = wrapInStringConcat().interpret(failAsError = data.inConstantExpression) fun IrExpression.getConstStringOrEmpty(): String = if (this is IrConst<*>) value.toString() else "" // If we have some complex expression in arguments (like some `IrComposite`) we will skip it, diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt index 1c299c79578..30233ccbe7e 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstOnlyNecessaryTransformer.kt @@ -11,16 +11,19 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.IrStatement import org.jetbrains.kotlin.ir.declarations.IrField import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.interpreter.IrInterpreter import org.jetbrains.kotlin.ir.interpreter.checker.EvaluationMode import org.jetbrains.kotlin.ir.interpreter.checker.IrInterpreterChecker import org.jetbrains.kotlin.ir.interpreter.isConst import org.jetbrains.kotlin.ir.interpreter.property +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.name.JsStandardClassIds /** - * This transformer will visit all expressions and will evaluate only those that are necessary. By "necessary" we mean expressions - * that are used in `const val` and inside annotations. + * This transformer will visit all expressions and will evaluate only those that are necessary. + * By "necessary" we mean expressions that are used in `const val`, inside annotations and js() call arguments. */ internal class IrConstOnlyNecessaryTransformer( interpreter: IrInterpreter, @@ -35,10 +38,13 @@ internal class IrConstOnlyNecessaryTransformer( ) : IrConstExpressionTransformer( interpreter, irFile, mode, checker, evaluatedConstTracker, inlineConstTracker, onWarning, onError, suppressExceptions ) { + private val jsCodeFqName = JsStandardClassIds.Callables.JsCode.asSingleFqName() + override fun visitCall(expression: IrCall, data: Data): IrElement { val isConstGetter = expression.symbol.owner.property.isConst - if (!data.inAnnotation && !isConstGetter) { - expression.transformChildren(this, data) + val isJsCodeCall = expression.symbol.owner.fqNameWhenAvailable == jsCodeFqName + if (isJsCodeCall || (!data.inConstantExpression && !isConstGetter)) { + expression.transformChildren(this, data.copy(inConstantExpression = data.inConstantExpression || isJsCodeCall)) return expression } return super.visitCall(expression, data) @@ -46,12 +52,12 @@ internal class IrConstOnlyNecessaryTransformer( override fun visitGetField(expression: IrGetField, data: Data): IrExpression { val isConst = expression.symbol.owner.property.isConst - if (!data.inAnnotation && !isConst) return expression + if (!data.inConstantExpression && !isConst) return expression return super.visitGetField(expression, data) } override fun visitStringConcatenation(expression: IrStringConcatenation, data: Data): IrExpression { - if (!data.inAnnotation) { + if (!data.inConstantExpression) { expression.transformChildren(this, data) return expression } diff --git a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt index c5333f885d4..dbb72293359 100644 --- a/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt +++ b/compiler/ir/ir.interpreter/src/org/jetbrains/kotlin/ir/interpreter/transformer/IrConstTransformer.kt @@ -99,7 +99,7 @@ internal abstract class IrConstTransformer( private val onError: (IrFile, IrElement, IrErrorExpression) -> Unit, private val suppressExceptions: Boolean, ) : IrElementTransformer { - internal data class Data(val inAnnotation: Boolean = false) + internal data class Data(val inConstantExpression: Boolean = false) private fun IrExpression.warningIfError(original: IrExpression): IrExpression { if (this is IrErrorExpression) { diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt index 4419383021a..6d61cc99175 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibCheckers.kt @@ -9,10 +9,15 @@ import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext import org.jetbrains.kotlin.config.CompilerConfiguration import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.diagnostics.DiagnosticReporter +import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibEsModuleExportsChecker import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibOtherModuleExportsChecker -import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName -import org.jetbrains.kotlin.ir.declarations.IrFile +import org.jetbrains.kotlin.ir.backend.js.checkers.expressions.JsKlibJsCodeCallChecker +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid +import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid +import org.jetbrains.kotlin.ir.visitors.acceptVoid import org.jetbrains.kotlin.library.SerializedIrFile object JsKlibCheckers { @@ -21,17 +26,51 @@ object JsKlibCheckers { JsKlibOtherModuleExportsChecker ) + private val callCheckers = listOf( + JsKlibJsCodeCallChecker + ) + fun check( cleanFiles: List, - dirtyFiles: List, + dirtyModule: IrModuleFragment, exportedNames: Map>, diagnosticReporter: DiagnosticReporter, configuration: CompilerConfiguration ) { - val reporter = KtDiagnosticReporterWithImplicitIrBasedContext(diagnosticReporter, configuration.languageVersionSettings) - val exportedDeclarations = JsKlibExportingDeclaration.collectDeclarations(cleanFiles, dirtyFiles, exportedNames) - for (checker in exportedDeclarationsCheckers) { - checker.check(exportedDeclarations, reporter) - } + dirtyModule.acceptVoid(object : IrElementVisitorVoid { + private val reporter = KtDiagnosticReporterWithImplicitIrBasedContext(diagnosticReporter, configuration.languageVersionSettings) + private val diagnosticContext = JsKlibDiagnosticContext(configuration) + + override fun visitElement(element: IrElement) { + if (element is IrDeclaration) { + diagnosticContext.withDeclarationScope(element) { + element.acceptChildrenVoid(this) + } + } else { + element.acceptChildrenVoid(this) + } + } + + override fun visitModuleFragment(declaration: IrModuleFragment) { + val exportedDeclarations = JsKlibExportingDeclaration.collectDeclarations(cleanFiles, declaration.files, exportedNames) + for (checker in exportedDeclarationsCheckers) { + checker.check(exportedDeclarations, this.diagnosticContext, reporter) + } + super.visitModuleFragment(declaration) + } + + override fun visitFile(declaration: IrFile) { + diagnosticContext.withFileScope(declaration) { + super.visitFile(declaration) + } + } + + override fun visitCall(expression: IrCall) { + for (checker in callCheckers) { + checker.check(expression, this.diagnosticContext, reporter) + } + super.visitCall(expression) + } + }) } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt index 6eb6dff902d..1ae9b6b686f 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDeclarationsChecker.kt @@ -8,7 +8,7 @@ package org.jetbrains.kotlin.ir.backend.js.checkers import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext interface JsKlibDeclarationsChecker { - fun check(declarations: List, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) + fun check(declarations: List, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) } typealias JsKlibExportedDeclarationsChecker = JsKlibDeclarationsChecker diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt new file mode 100644 index 00000000000..937667a0d1e --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibDiagnosticContext.kt @@ -0,0 +1,66 @@ +/* + * 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.ir.backend.js.checkers + +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.diagnostics.KtDiagnosticReporterWithContext +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.declarations.IrFile + +class JsKlibDiagnosticContext(val compilerConfiguration: CompilerConfiguration) { + var containingDeclaration: IrDeclaration? = null + private set + + var containingFile: IrFile? = null + private set + + fun withDeclarationScope(declaration: IrDeclaration, f: () -> Unit) { + val prevDeclaration = containingDeclaration + try { + containingDeclaration = declaration + f() + } finally { + containingDeclaration = prevDeclaration + } + } + + fun withFileScope(file: IrFile, f: () -> Unit) { + val prevFile = containingFile + try { + containingFile = file + f() + } finally { + containingFile = prevFile + } + } +} + +fun KtDiagnosticReporterWithImplicitIrBasedContext.at( + declaration: IrDeclaration, + context: JsKlibDiagnosticContext, +): KtDiagnosticReporterWithContext.DiagnosticContextImpl { + return context.containingFile?.let { at(declaration, it) } ?: at(declaration) +} + +fun KtDiagnosticReporterWithImplicitIrBasedContext.at( + irElement: IrElement, + context: JsKlibDiagnosticContext, +): KtDiagnosticReporterWithContext.DiagnosticContextImpl { + val file = context.containingFile + if (file != null) { + return at(irElement, file) + } + + val declaration = context.containingDeclaration + if (declaration != null) { + return at(irElement, declaration) + } + + // Should never happen + error("Cannot find the expression containing declaration") +} diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt index db08243c547..ceb63ba05fd 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibErrors.kt @@ -7,14 +7,23 @@ package org.jetbrains.kotlin.ir.backend.js.checkers import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap +import org.jetbrains.kotlin.diagnostics.error0 +import org.jetbrains.kotlin.diagnostics.error1 import org.jetbrains.kotlin.diagnostics.error2 import org.jetbrains.kotlin.diagnostics.rendering.* +import org.jetbrains.kotlin.diagnostics.warning0 +import org.jetbrains.kotlin.diagnostics.warning1 import org.jetbrains.kotlin.diagnostics.warning2 object JsKlibErrors { val EXPORTING_JS_NAME_CLASH by error2>() val EXPORTING_JS_NAME_CLASH_ES by warning2>() + val JSCODE_CAN_NOT_VERIFY_JAVASCRIPT by warning0() + val JSCODE_NO_JAVASCRIPT_PRODUCED by error0() + val JSCODE_ERROR by error1() + val JSCODE_WARNING by warning1() + init { RootDiagnosticRendererFactory.registerFactory(KtDefaultJsKlibErrorMessages) } @@ -43,5 +52,23 @@ private object KtDefaultJsKlibErrorMessages : BaseDiagnosticRendererFactory() { CommonRenderers.STRING, JS_KLIB_EXPORTS, ) + map.put( + JsKlibErrors.JSCODE_CAN_NOT_VERIFY_JAVASCRIPT, + "Cannot verify JavaScript code because the argument is not a constant string" + ) + map.put( + JsKlibErrors.JSCODE_NO_JAVASCRIPT_PRODUCED, + "An argument for the js() function must be non-empty JavaScript code" + ) + map.put( + JsKlibErrors.JSCODE_ERROR, + "JavaScript error: {0}", + CommonRenderers.STRING + ) + map.put( + JsKlibErrors.JSCODE_WARNING, + "JavaScript warning: {0}", + CommonRenderers.STRING + ) } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt new file mode 100644 index 00000000000..cc5f0374153 --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/JsKlibExpressionChecker.kt @@ -0,0 +1,16 @@ +/* + * 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.ir.backend.js.checkers + +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression + +interface JsKlibExpressionChecker { + fun check(expression: E, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) +} + +typealias JsKlibCallChecker = JsKlibExpressionChecker diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt index d039a1f7237..62fc10d2da1 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibEsModuleExportsChecker.kt @@ -6,14 +6,13 @@ package org.jetbrains.kotlin.ir.backend.js.checkers.declarations import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportingDeclaration -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportedDeclarationsChecker -import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibErrors +import org.jetbrains.kotlin.ir.backend.js.checkers.* object JsKlibEsModuleExportsChecker : JsKlibExportedDeclarationsChecker { override fun check( declarations: List, - reporter: KtDiagnosticReporterWithImplicitIrBasedContext, + context: JsKlibDiagnosticContext, + reporter: KtDiagnosticReporterWithImplicitIrBasedContext ) { val allExportedNameClashes = declarations.groupBy { it.exportingName }.filterValues { it.size > 1 } @@ -21,7 +20,11 @@ object JsKlibEsModuleExportsChecker : JsKlibExportedDeclarationsChecker { for ((index, exportedDeclaration) in exportedDeclarationClashes.withIndex()) { val declaration = exportedDeclaration.declaration ?: continue val clashedWith = exportedDeclarationClashes.filterIndexed { i, _ -> i != index } - reporter.at(declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES, exportedDeclaration.exportingName, clashedWith) + reporter.at(declaration, context).report( + JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES, + exportedDeclaration.exportingName, + clashedWith + ) } } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt index c1a9d5de670..5a0da36f487 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/declarations/JsKlibOtherModuleExportsChecker.kt @@ -44,11 +44,19 @@ object JsKlibOtherModuleExportsChecker : JsKlibExportedDeclarationsChecker { } } - override fun check(declarations: List, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) { + override fun check( + declarations: List, + context: JsKlibDiagnosticContext, + reporter: KtDiagnosticReporterWithImplicitIrBasedContext + ) { val clashes = collectClashes(declarations) for ((declaration, clashedWith) in clashes) { if (declaration.declaration != null) { - reporter.at(declaration.declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH, declaration.exportingName, clashedWith) + reporter.at(declaration.declaration, context).report( + JsKlibErrors.EXPORTING_JS_NAME_CLASH, + declaration.exportingName, + clashedWith + ) } } } diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt new file mode 100644 index 00000000000..2598f1b25e1 --- /dev/null +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/checkers/expressions/JsKlibJsCodeCallChecker.kt @@ -0,0 +1,80 @@ +/* + * 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.ir.backend.js.checkers.expressions + +import com.google.gwt.dev.js.parserExceptions.AbortParsingException +import com.google.gwt.dev.js.rhino.CodePosition +import com.google.gwt.dev.js.rhino.ErrorReporter +import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext +import org.jetbrains.kotlin.config.languageVersionSettings +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibCallChecker +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibErrors +import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibDiagnosticContext +import org.jetbrains.kotlin.ir.backend.js.checkers.at +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrConstKind +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable +import org.jetbrains.kotlin.js.backend.ast.JsFunctionScope +import org.jetbrains.kotlin.js.backend.ast.JsProgram +import org.jetbrains.kotlin.js.backend.ast.JsRootScope +import org.jetbrains.kotlin.js.parser.parseExpressionOrStatement +import org.jetbrains.kotlin.name.JsStandardClassIds + +object JsKlibJsCodeCallChecker : JsKlibCallChecker { + private val jsCodeFqName = JsStandardClassIds.Callables.JsCode.asSingleFqName() + + override fun check(expression: IrCall, context: JsKlibDiagnosticContext, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) { + // Do not check IR from K1, because there are corresponding K1 FE checks in JsCallChecker + if (!context.compilerConfiguration.languageVersionSettings.languageVersion.usesK2) { + return + } + if (expression.symbol.owner.fqNameWhenAvailable != jsCodeFqName) { + return + } + + val jsCodeExpr = expression.getValueArgument(0) + // K2 frontend checker FirJsCodeConstantArgumentChecker checks that the passing argument is a constant. + // IrConstOnlyNecessaryTransformer must evaluate an argument expression and fold it into a string constant. + if (jsCodeExpr !is IrConst<*> || jsCodeExpr.kind != IrConstKind.String) { + // Do not ignore the error if the argument does not fit the expectations. + // The IR can be generated in the plugin, avoiding FE checks and IrConstOnlyNecessaryTransformer. + // Bugs are also possible in IrConstOnlyNecessaryTransformer. + reporter.at(jsCodeExpr ?: expression, context).report(JsKlibErrors.JSCODE_CAN_NOT_VERIFY_JAVASCRIPT) + return + } + + val jsCodeStr = IrConstKind.String.valueOf(jsCodeExpr) + + try { + val parserScope = JsFunctionScope(JsRootScope(JsProgram()), "") + val fileName = context.containingFile?.fileEntry?.name ?: "" + val jsErrorReporter = JsErrorReporter(jsCodeExpr, context, reporter) + val statements = parseExpressionOrStatement(jsCodeStr, jsErrorReporter, parserScope, CodePosition(0, 0), fileName) + if (statements.isNullOrEmpty()) { + reporter.at(jsCodeExpr, context).report(JsKlibErrors.JSCODE_NO_JAVASCRIPT_PRODUCED) + } + } catch (e: AbortParsingException) { + // ignore + } + } + + private class JsErrorReporter( + val codeExpression: IrExpression, + val context: JsKlibDiagnosticContext, + val reporter: KtDiagnosticReporterWithImplicitIrBasedContext, + ) : ErrorReporter { + override fun warning(message: String, startPosition: CodePosition, endPosition: CodePosition) { + reporter.at(codeExpression, context).report(JsKlibErrors.JSCODE_WARNING, message) + } + + override fun error(message: String, startPosition: CodePosition, endPosition: CodePosition) { + reporter.at(codeExpression, context).report(JsKlibErrors.JSCODE_ERROR, message) + throw AbortParsingException() + } + } +} diff --git a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt index dcec50eb8c9..e69ef210b3a 100644 --- a/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt +++ b/compiler/ir/serialization.js/src/org/jetbrains/kotlin/ir/backend/js/klib.kt @@ -656,7 +656,7 @@ fun serializeModuleIntoKlib( if (builtInsPlatform == BuiltInsPlatform.JS) { val cleanFilesIrData = cleanFiles.map { it.irData } - JsKlibCheckers.check(cleanFilesIrData, moduleFragment.files, moduleExportedNames, diagnosticReporter, configuration) + JsKlibCheckers.check(cleanFilesIrData, moduleFragment, moduleExportedNames, diagnosticReporter, configuration) } val serializedIr =