[JS IR] Detect broken cross-module references
The patch adds an error if the module can not find the cross-module reference. The patch removes the DCE optimization which eliminates implement() intrinsic, because it leads to a broken cross-module reference and broken JS code with implement() call, albeit in an unreachable block.
This commit is contained in:
committed by
Space Team
parent
e6efde76dc
commit
155777e3fa
+1
-8
@@ -27,13 +27,6 @@ internal class JsUsefulDeclarationProcessor(
|
||||
private val hashCodeMethod = getMethodOfAny("hashCode")
|
||||
|
||||
override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() {
|
||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: IrDeclaration) {
|
||||
if (expression.symbol != context.intrinsics.implementSymbol) {
|
||||
// Just ignore implement to not include large chunk of code inside small applications if it's not needed
|
||||
super.visitFunctionAccess(expression, data)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall, data: IrDeclaration) {
|
||||
super.visitCall(expression, data)
|
||||
when (expression.symbol) {
|
||||
@@ -259,4 +252,4 @@ private fun Collection<IrClass>.filterDescendantsOf(bases: Collection<IrClass>):
|
||||
}
|
||||
|
||||
return this.filter { overridesAnyBase(it) }
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -20,7 +20,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
}
|
||||
|
||||
private enum class NameType(val typeMask: Int) {
|
||||
DEFINITIONS(0b01), NAME_BINDINGS(0b10)
|
||||
DEFINITIONS(0b1), NAME_BINDINGS(0b10), OPTIONAL_IMPORTS(0b100)
|
||||
}
|
||||
|
||||
class CachedModuleInfo(val artifact: ModuleArtifact, val jsIrHeader: JsIrModuleHeader, var crossModuleReferencesHash: ICHash = ICHash())
|
||||
@@ -30,6 +30,7 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
private fun ModuleArtifact.fetchModuleInfo() = File(artifactsDir, JS_MODULE_HEADER).useCodedInputIfExists {
|
||||
val definitions = mutableSetOf<String>()
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
|
||||
val crossModuleReferencesHash = ICHash.fromProtoStream(this)
|
||||
val hasJsExports = readBool()
|
||||
@@ -39,13 +40,24 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
if (mask and NameType.DEFINITIONS.typeMask != 0) {
|
||||
definitions += tag
|
||||
}
|
||||
if (mask and NameType.OPTIONAL_IMPORTS.typeMask != 0) {
|
||||
optionalCrossModuleImports += tag
|
||||
}
|
||||
if (mask and NameType.NAME_BINDINGS.typeMask != 0) {
|
||||
nameBindings[tag] = readString()
|
||||
}
|
||||
}
|
||||
CachedModuleInfo(
|
||||
artifact = this@fetchModuleInfo,
|
||||
jsIrHeader = JsIrModuleHeader(moduleSafeName, moduleExternalName, definitions, nameBindings, hasJsExports, null),
|
||||
jsIrHeader = JsIrModuleHeader(
|
||||
moduleName = moduleSafeName,
|
||||
externalModuleName = moduleExternalName,
|
||||
definitions = definitions,
|
||||
nameBindings = nameBindings,
|
||||
optionalCrossModuleImports = optionalCrossModuleImports,
|
||||
hasJsExports = hasJsExports,
|
||||
associatedModule = null
|
||||
),
|
||||
crossModuleReferencesHash = crossModuleReferencesHash
|
||||
)
|
||||
}
|
||||
@@ -56,6 +68,10 @@ class JsMultiModuleCache(private val moduleArtifacts: List<ModuleArtifact>) {
|
||||
for ((tag, name) in jsIrHeader.nameBindings) {
|
||||
names[tag] = NameType.NAME_BINDINGS.typeMask to name
|
||||
}
|
||||
for (tag in jsIrHeader.optionalCrossModuleImports) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.OPTIONAL_IMPORTS.typeMask) to maskAndName?.second
|
||||
}
|
||||
for (tag in jsIrHeader.definitions) {
|
||||
val maskAndName = names[tag]
|
||||
names[tag] = ((maskAndName?.first ?: 0) or NameType.DEFINITIONS.typeMask) to maskAndName?.second
|
||||
|
||||
+6
@@ -5,11 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionInterface
|
||||
import org.jetbrains.kotlin.config.CommonConfigurationKeys
|
||||
import org.jetbrains.kotlin.ir.backend.js.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations
|
||||
import org.jetbrains.kotlin.ir.backend.js.export.*
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.isBuiltInClass
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.util.isInterface
|
||||
@@ -321,12 +323,16 @@ class IrModuleToJsTransformer(
|
||||
nameGenerator.nameMap.entries.forEach { (declaration, name) ->
|
||||
computeTag(declaration)?.let { tag ->
|
||||
result.nameBindings[tag] = name
|
||||
if (isBuiltInClass(declaration) || checkIsFunctionInterface(declaration.symbol.signature)) {
|
||||
result.optionalCrossModuleImports += tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nameGenerator.imports.entries.forEach { (declaration, importExpression) ->
|
||||
val tag = computeTag(declaration) ?: error("No tag for imported declaration ${declaration.render()}")
|
||||
result.imports[tag] = importExpression
|
||||
result.optionalCrossModuleImports += tag
|
||||
}
|
||||
|
||||
fileExports.file.declarations.forEach {
|
||||
|
||||
+22
-6
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.serialization.js.ModuleKind
|
||||
|
||||
class JsIrProgramFragment(val packageFqn: String) {
|
||||
val nameBindings = mutableMapOf<String, JsName>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
val declarations = JsCompositeBlock()
|
||||
val exports = JsCompositeBlock()
|
||||
val importedModules = mutableListOf<JsImportedModule>()
|
||||
@@ -35,6 +36,7 @@ class JsIrModule(
|
||||
fun makeModuleHeader(): JsIrModuleHeader {
|
||||
val nameBindings = mutableMapOf<String, String>()
|
||||
val definitions = mutableSetOf<String>()
|
||||
val optionalCrossModuleImports = hashSetOf<String>()
|
||||
var hasJsExports = false
|
||||
for (fragment in fragments) {
|
||||
hasJsExports = hasJsExports || !fragment.exports.isEmpty
|
||||
@@ -42,8 +44,17 @@ class JsIrModule(
|
||||
nameBindings[tag] = name.toString()
|
||||
}
|
||||
definitions += fragment.definitions
|
||||
optionalCrossModuleImports += fragment.optionalCrossModuleImports
|
||||
}
|
||||
return JsIrModuleHeader(moduleName, externalModuleName, definitions, nameBindings, hasJsExports, this)
|
||||
return JsIrModuleHeader(
|
||||
moduleName = moduleName,
|
||||
externalModuleName = externalModuleName,
|
||||
definitions = definitions,
|
||||
nameBindings = nameBindings,
|
||||
optionalCrossModuleImports = optionalCrossModuleImports,
|
||||
hasJsExports = hasJsExports,
|
||||
associatedModule = this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +63,7 @@ class JsIrModuleHeader(
|
||||
val externalModuleName: String,
|
||||
val definitions: Set<String>,
|
||||
val nameBindings: Map<String, String>,
|
||||
val optionalCrossModuleImports: Set<String>,
|
||||
val hasJsExports: Boolean,
|
||||
var associatedModule: JsIrModule?
|
||||
) {
|
||||
@@ -100,7 +112,13 @@ class CrossModuleDependenciesResolver(
|
||||
for (header in headers) {
|
||||
val builder = headerToBuilder[header]!!
|
||||
for (tag in header.externalNames) {
|
||||
val fromModuleBuilder = definitionModule[tag] ?: continue // TODO error?
|
||||
val fromModuleBuilder = definitionModule[tag]
|
||||
if (fromModuleBuilder == null) {
|
||||
if (tag in header.optionalCrossModuleImports) {
|
||||
continue
|
||||
}
|
||||
error("Internal error: cannot find external signature '$tag' for module ${header.moduleName}")
|
||||
}
|
||||
|
||||
builder.imports += CrossModuleRef(fromModuleBuilder, tag)
|
||||
fromModuleBuilder.exports += tag
|
||||
@@ -111,8 +129,6 @@ class CrossModuleDependenciesResolver(
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.prettyTag() = takeWhile { c -> c != '|' }
|
||||
|
||||
private class CrossModuleRef(val module: JsIrModuleCrossModuleReferecenceBuilder, val tag: String)
|
||||
|
||||
private class JsIrModuleCrossModuleReferecenceBuilder(
|
||||
@@ -153,7 +169,7 @@ private class JsIrModuleCrossModuleReferecenceBuilder(
|
||||
val tag = crossModuleRef.tag
|
||||
require(crossModuleRef.module::exportNames.isInitialized) {
|
||||
// This situation appears in case of a dependent module redefine a symbol (function) from their dependency
|
||||
"Cross module dependency resolution failed due to symbol '${tag.prettyTag()}' redefinition"
|
||||
"Cross module dependency resolution failed due to signature '$tag' redefinition"
|
||||
}
|
||||
val exportedAs = crossModuleRef.module.exportNames[tag]!!
|
||||
val moduleName = import(crossModuleRef.module.header)
|
||||
@@ -207,7 +223,7 @@ class CrossModuleReferences(
|
||||
fun initJsImportsForModule(module: JsIrModule) {
|
||||
val tagToName = module.fragments.flatMap { it.nameBindings.entries }.associate { it.key to it.value }
|
||||
jsImports = imports.entries.associate {
|
||||
val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for symbol ${it.key.prettyTag()}")
|
||||
val importedAs = tagToName[it.key] ?: error("Internal error: cannot find imported name for signature ${it.key}")
|
||||
val exportRef = JsNameRef(
|
||||
it.value.exportedAs,
|
||||
it.value.moduleExporter.let {
|
||||
|
||||
+2
@@ -68,6 +68,8 @@ class JsIrAstDeserializer : JsAstDeserializerBase() {
|
||||
deserializeString(nameBindingProto.signatureId) to deserializeName(nameBindingProto.nameId)
|
||||
}
|
||||
|
||||
proto.optionalCrossModuleImportsList.mapTo(fragment.optionalCrossModuleImports) { deserializeString(it) }
|
||||
|
||||
proto.irClassModelList.associateTo(fragment.classes) { clsProto -> deserialize(clsProto) }
|
||||
|
||||
if (proto.hasTestsInvocation()) {
|
||||
|
||||
+4
@@ -70,6 +70,10 @@ class JsIrAstSerializer: JsAstSerializerBase() {
|
||||
fragmentBuilder.addNameBinding(nameBindingBuilder)
|
||||
}
|
||||
|
||||
fragment.optionalCrossModuleImports.forEach {
|
||||
fragmentBuilder.addOptionalCrossModuleImports(serialize(it))
|
||||
}
|
||||
|
||||
fragment.classes.entries.forEach { (name, model) -> fragmentBuilder.addIrClassModel(serialize(name, model)) }
|
||||
|
||||
fragment.testFunInvocation?.let {
|
||||
|
||||
+9
@@ -5,8 +5,17 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.common.serialization
|
||||
|
||||
import org.jetbrains.kotlin.ir.util.IdSignature
|
||||
import java.util.regex.Pattern
|
||||
|
||||
internal val functionPattern = Pattern.compile("^K?(Suspend)?Function\\d+$")
|
||||
|
||||
internal val functionalPackages = listOf("kotlin", "kotlin.coroutines", "kotlin.reflect")
|
||||
|
||||
fun checkIsFunctionInterface(idSig: IdSignature?): Boolean {
|
||||
val publicSig = idSig?.asPublic()
|
||||
return publicSig != null &&
|
||||
publicSig.packageFqName in functionalPackages &&
|
||||
publicSig.declarationFqName.isNotEmpty() &&
|
||||
functionPattern.matcher(publicSig.firstNameSegment).find()
|
||||
}
|
||||
|
||||
-8
@@ -124,14 +124,6 @@ class IrModuleDeserializerWithBuiltIns(
|
||||
symbol.signature to symbol
|
||||
}.toMap()
|
||||
|
||||
private fun checkIsFunctionInterface(idSig: IdSignature): Boolean {
|
||||
val publicSig = idSig.asPublic()
|
||||
return publicSig != null &&
|
||||
publicSig.packageFqName in functionalPackages &&
|
||||
publicSig.declarationFqName.isNotEmpty() &&
|
||||
functionPattern.matcher(publicSig.firstNameSegment).find()
|
||||
}
|
||||
|
||||
override operator fun contains(idSig: IdSignature): Boolean {
|
||||
val topLevel = idSig.topLevelSignature()
|
||||
if (topLevel in irBuiltInsMap) return true
|
||||
|
||||
Reference in New Issue
Block a user