[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:
Alexander Korepanov
2023-01-04 17:06:26 +01:00
committed by Space Team
parent e6efde76dc
commit 155777e3fa
10 changed files with 219 additions and 25 deletions
@@ -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) }
}
}
@@ -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
@@ -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 {
@@ -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 {
@@ -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()) {
@@ -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 {
@@ -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()
}
@@ -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
+2 -1
View File
@@ -419,6 +419,7 @@ message Fragment {
optional int32 suite_function = 16;
repeated int32 definitions = 17;
optional CompositeBlock polyfills = 18;
repeated int32 optionalCrossModuleImports = 19;
}
message InlinedLocalDeclarations {
@@ -514,4 +515,4 @@ message Chunk {
message InlineData {
repeated string inline_function_tags = 1;
}
}
@@ -33017,6 +33017,19 @@ public final class JsAstProtoBuf {
* <code>optional .org.jetbrains.kotlin.serialization.js.ast.CompositeBlock polyfills = 18;</code>
*/
org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.CompositeBlock getPolyfills();
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
java.util.List<java.lang.Integer> getOptionalCrossModuleImportsList();
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
int getOptionalCrossModuleImportsCount();
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
int getOptionalCrossModuleImports(int index);
}
/**
* Protobuf type {@code org.jetbrains.kotlin.serialization.js.ast.Fragment}
@@ -33248,6 +33261,27 @@ public final class JsAstProtoBuf {
bitField0_ |= 0x00000100;
break;
}
case 152: {
if (!((mutable_bitField0_ & 0x00040000) == 0x00040000)) {
optionalCrossModuleImports_ = new java.util.ArrayList<java.lang.Integer>();
mutable_bitField0_ |= 0x00040000;
}
optionalCrossModuleImports_.add(input.readInt32());
break;
}
case 154: {
int length = input.readRawVarint32();
int limit = input.pushLimit(length);
if (!((mutable_bitField0_ & 0x00040000) == 0x00040000) && input.getBytesUntilLimit() > 0) {
optionalCrossModuleImports_ = new java.util.ArrayList<java.lang.Integer>();
mutable_bitField0_ |= 0x00040000;
}
while (input.getBytesUntilLimit() > 0) {
optionalCrossModuleImports_.add(input.readInt32());
}
input.popLimit(limit);
break;
}
}
}
} catch (org.jetbrains.kotlin.protobuf.InvalidProtocolBufferException e) {
@@ -33283,6 +33317,9 @@ public final class JsAstProtoBuf {
if (((mutable_bitField0_ & 0x00010000) == 0x00010000)) {
definitions_ = java.util.Collections.unmodifiableList(definitions_);
}
if (((mutable_bitField0_ & 0x00040000) == 0x00040000)) {
optionalCrossModuleImports_ = java.util.Collections.unmodifiableList(optionalCrossModuleImports_);
}
try {
unknownFieldsCodedOutput.flush();
} catch (java.io.IOException e) {
@@ -33800,6 +33837,28 @@ public final class JsAstProtoBuf {
return polyfills_;
}
public static final int OPTIONALCROSSMODULEIMPORTS_FIELD_NUMBER = 19;
private java.util.List<java.lang.Integer> optionalCrossModuleImports_;
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public java.util.List<java.lang.Integer>
getOptionalCrossModuleImportsList() {
return optionalCrossModuleImports_;
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public int getOptionalCrossModuleImportsCount() {
return optionalCrossModuleImports_.size();
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public int getOptionalCrossModuleImports(int index) {
return optionalCrossModuleImports_.get(index);
}
private void initFields() {
importedModule_ = java.util.Collections.emptyList();
importEntry_ = java.util.Collections.emptyList();
@@ -33819,6 +33878,7 @@ public final class JsAstProtoBuf {
suiteFunction_ = 0;
definitions_ = java.util.Collections.emptyList();
polyfills_ = org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.CompositeBlock.getDefaultInstance();
optionalCrossModuleImports_ = java.util.Collections.emptyList();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -33971,6 +34031,9 @@ public final class JsAstProtoBuf {
if (((bitField0_ & 0x00000100) == 0x00000100)) {
output.writeMessage(18, polyfills_);
}
for (int i = 0; i < optionalCrossModuleImports_.size(); i++) {
output.writeInt32(19, optionalCrossModuleImports_.get(i));
}
output.writeRawBytes(unknownFields);
}
@@ -34057,6 +34120,15 @@ public final class JsAstProtoBuf {
size += org.jetbrains.kotlin.protobuf.CodedOutputStream
.computeMessageSize(18, polyfills_);
}
{
int dataSize = 0;
for (int i = 0; i < optionalCrossModuleImports_.size(); i++) {
dataSize += org.jetbrains.kotlin.protobuf.CodedOutputStream
.computeInt32SizeNoTag(optionalCrossModuleImports_.get(i));
}
size += dataSize;
size += 2 * getOptionalCrossModuleImportsList().size();
}
size += unknownFields.size();
memoizedSerializedSize = size;
return size;
@@ -34187,6 +34259,8 @@ public final class JsAstProtoBuf {
bitField0_ = (bitField0_ & ~0x00010000);
polyfills_ = org.jetbrains.kotlin.serialization.js.ast.JsAstProtoBuf.CompositeBlock.getDefaultInstance();
bitField0_ = (bitField0_ & ~0x00020000);
optionalCrossModuleImports_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00040000);
return this;
}
@@ -34291,6 +34365,11 @@ public final class JsAstProtoBuf {
to_bitField0_ |= 0x00000100;
}
result.polyfills_ = polyfills_;
if (((bitField0_ & 0x00040000) == 0x00040000)) {
optionalCrossModuleImports_ = java.util.Collections.unmodifiableList(optionalCrossModuleImports_);
bitField0_ = (bitField0_ & ~0x00040000);
}
result.optionalCrossModuleImports_ = optionalCrossModuleImports_;
result.bitField0_ = to_bitField0_;
return result;
}
@@ -34418,6 +34497,16 @@ public final class JsAstProtoBuf {
if (other.hasPolyfills()) {
mergePolyfills(other.getPolyfills());
}
if (!other.optionalCrossModuleImports_.isEmpty()) {
if (optionalCrossModuleImports_.isEmpty()) {
optionalCrossModuleImports_ = other.optionalCrossModuleImports_;
bitField0_ = (bitField0_ & ~0x00040000);
} else {
ensureOptionalCrossModuleImportsIsMutable();
optionalCrossModuleImports_.addAll(other.optionalCrossModuleImports_);
}
}
setUnknownFields(
getUnknownFields().concat(other.unknownFields));
return this;
@@ -36140,6 +36229,72 @@ public final class JsAstProtoBuf {
return this;
}
private java.util.List<java.lang.Integer> optionalCrossModuleImports_ = java.util.Collections.emptyList();
private void ensureOptionalCrossModuleImportsIsMutable() {
if (!((bitField0_ & 0x00040000) == 0x00040000)) {
optionalCrossModuleImports_ = new java.util.ArrayList<java.lang.Integer>(optionalCrossModuleImports_);
bitField0_ |= 0x00040000;
}
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public java.util.List<java.lang.Integer>
getOptionalCrossModuleImportsList() {
return java.util.Collections.unmodifiableList(optionalCrossModuleImports_);
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public int getOptionalCrossModuleImportsCount() {
return optionalCrossModuleImports_.size();
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public int getOptionalCrossModuleImports(int index) {
return optionalCrossModuleImports_.get(index);
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public Builder setOptionalCrossModuleImports(
int index, int value) {
ensureOptionalCrossModuleImportsIsMutable();
optionalCrossModuleImports_.set(index, value);
return this;
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public Builder addOptionalCrossModuleImports(int value) {
ensureOptionalCrossModuleImportsIsMutable();
optionalCrossModuleImports_.add(value);
return this;
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public Builder addAllOptionalCrossModuleImports(
java.lang.Iterable<? extends java.lang.Integer> values) {
ensureOptionalCrossModuleImportsIsMutable();
org.jetbrains.kotlin.protobuf.AbstractMessageLite.Builder.addAll(
values, optionalCrossModuleImports_);
return this;
}
/**
* <code>repeated int32 optionalCrossModuleImports = 19;</code>
*/
public Builder clearOptionalCrossModuleImports() {
optionalCrossModuleImports_ = java.util.Collections.emptyList();
bitField0_ = (bitField0_ & ~0x00040000);
return this;
}
// @@protoc_insertion_point(builder_scope:org.jetbrains.kotlin.serialization.js.ast.Fragment)
}