diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java index 5e0b36edb67..542c9876ace 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/JvmCodegenUtil.java @@ -20,6 +20,8 @@ import org.jetbrains.kotlin.codegen.context.MethodContext; import org.jetbrains.kotlin.codegen.context.RootContext; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.config.JvmAnalysisFlags; +import org.jetbrains.kotlin.config.LanguageVersionSettings; import org.jetbrains.kotlin.descriptors.*; import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor; import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor; @@ -380,4 +382,13 @@ public class JvmCodegenUtil { public static boolean isPolymorphicSignature(@NotNull FunctionDescriptor descriptor) { return descriptor.getAnnotations().hasAnnotation(new FqName("java.lang.invoke.MethodHandle.PolymorphicSignature")); } + + @NotNull + public static String sanitizeNameIfNeeded(@NotNull String name, @NotNull LanguageVersionSettings languageVersionSettings) { + if (languageVersionSettings.getFlag(JvmAnalysisFlags.getSanitizeParentheses())) { + return name.replace("(", "$_").replace(")", "$_"); + } + + return name; + } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java index fff3d1ff96f..3e6b0d098ca 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java @@ -443,7 +443,8 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { @NotNull private MutableClosure recordClosure(@NotNull ClassDescriptor classDescriptor, @NotNull String name) { - return CodegenBinding.recordClosure(bindingTrace, classDescriptor, getProperEnclosingClass(), Type.getObjectType(name)); + Type type = Type.getObjectType(JvmCodegenUtil.sanitizeNameIfNeeded(name, languageVersionSettings)); + return CodegenBinding.recordClosure(bindingTrace, classDescriptor, getProperEnclosingClass(), type); } @Nullable diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index f5b89d8368e..70f5eec8d61 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -1156,6 +1156,8 @@ public class KotlinTypeMapper { name += JvmAbi.IMPL_SUFFIX_FOR_INLINE_CLASS_MEMBERS; } + name = JvmCodegenUtil.sanitizeNameIfNeeded(name, languageVersionSettings); + if (DescriptorUtils.isTopLevelDeclaration(descriptor)) { if (Visibilities.isPrivate(descriptor.getVisibility()) && !(descriptor instanceof ConstructorDescriptor) && !"".equals(name)) { String partName = getPartSimpleNameForMangling(descriptor); diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index 2613eeb36eb..a2be4846baa 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -252,6 +252,14 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var strictMetadataVersionSemantics: Boolean by FreezableVar(false) + @Argument( + value = "-Xsanitize-parentheses", + description = "Transform '(' and ')' in method names to some other character sequence.\n" + + "This mode can BREAK BINARY COMPATIBILITY and is only supposed to be used as a workaround\n" + + "of an issue in the ASM bytecode framework. See KT-29475 for more details" + ) + var sanitizeParentheses: Boolean by FreezableVar(false) + @Argument( value = "-Xfriend-paths", valueDescription = "", @@ -274,6 +282,7 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { "supported modes: ${JvmDefaultMode.values().map { it.description }}" ) result[JvmAnalysisFlags.inheritMultifileParts] = inheritMultifileParts + result[JvmAnalysisFlags.sanitizeParentheses] = sanitizeParentheses return result } diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JvmAnalysisFlags.kt b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JvmAnalysisFlags.kt index bb6021738bf..94c67d2cb8e 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JvmAnalysisFlags.kt +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JvmAnalysisFlags.kt @@ -17,4 +17,7 @@ object JvmAnalysisFlags { @JvmStatic val inheritMultifileParts by AnalysisFlag.Delegates.Boolean + + @JvmStatic + val sanitizeParentheses by AnalysisFlag.Delegates.Boolean } diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index d5a6fe41095..135b433aead 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -48,6 +48,9 @@ where advanced options include: -Xno-optimize Disable optimizations -Xno-param-assertions Don't generate not-null assertions on parameters of methods accessible from Java -Xno-receiver-assertions Don't generate not-null assertion for extension receiver arguments of platform types + -Xsanitize-parentheses Transform '(' and ')' in method names to some other character sequence. + This mode can BREAK BINARY COMPATIBILITY and is only supposed to be used as a workaround + of an issue in the ASM bytecode framework. See KT-29475 for more details -Xscript-resolver-environment= Script resolver environment in key-value pairs (the value could be quoted and escaped) -Xsingle-module Combine modules for source files and binary dependencies into a single module diff --git a/compiler/testData/codegen/box/mangling/parentheses.kt b/compiler/testData/codegen/box/mangling/parentheses.kt new file mode 100644 index 00000000000..2b6e15d73b7 --- /dev/null +++ b/compiler/testData/codegen/box/mangling/parentheses.kt @@ -0,0 +1,24 @@ +// !SANITIZE_PARENTHESES +// IGNORE_BACKEND: JS, JS_IR + +// Sanitization is needed here because of an ASM bug: https://gitlab.ow2.org/asm/asm/issues/317868 +// As soon as that bug is fixed and we've updated to the new version of ASM, this test will start to pass without sanitization. +// At that point, we should remove the -Xsanitize-parentheses compiler argument. +// Also don't forget to disable this test on Android where parentheses are not allowed in names + +class `()` { + fun `()`(): String { + fun foo(): String { + return bar { baz() } + } + return foo() + } + + fun baz() = "OK" +} + +fun bar(p: () -> String) = p() + +fun box(): String { + return `()`().`()`() +} diff --git a/compiler/testData/codegen/bytecodeText/mangling/parentheses.kt b/compiler/testData/codegen/bytecodeText/mangling/parentheses.kt new file mode 100644 index 00000000000..5a22d0c0de6 --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/mangling/parentheses.kt @@ -0,0 +1,23 @@ +// !SANITIZE_PARENTHESES +// IGNORE_BACKEND: JVM_IR + +class `(X)` { + fun `(Y)`(): String { + fun foo(): String { + return bar { baz() } + } + return foo() + } + + fun baz() = "OK" +} + +fun bar(p: () -> String) = p() + +fun box(): String { + return `(X)`().`(Y)`() +} + +// One instance of each is in kotlin.Metadata.d2 +// 1 \(X\) +// 1 \(Y\) diff --git a/compiler/testData/codegen/bytecodeText/mangling/parenthesesNoSanitize.kt b/compiler/testData/codegen/bytecodeText/mangling/parenthesesNoSanitize.kt new file mode 100644 index 00000000000..822e25df3df --- /dev/null +++ b/compiler/testData/codegen/bytecodeText/mangling/parenthesesNoSanitize.kt @@ -0,0 +1,11 @@ +// IGNORE_BACKEND: JVM_IR + +class `(X)` { + fun `(Y)`() {} +} + +// One in the file name, one in the class header, two in local variables in the constructor and the method, and one in kotlin.Metadata.d2 +// 5 \(X\) + +// One in the method header and one in kotlin.Metadata.d2 +// 2 \(Y\) diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CompilerTestLanguageVersionSettings.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CompilerTestLanguageVersionSettings.kt index b5470a9239f..28b8e5704ba 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CompilerTestLanguageVersionSettings.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/checkers/CompilerTestLanguageVersionSettings.kt @@ -22,6 +22,7 @@ const val JVM_DEFAULT_MODE = "JVM_DEFAULT_MODE" const val SKIP_METADATA_VERSION_CHECK = "SKIP_METADATA_VERSION_CHECK" const val ALLOW_RESULT_RETURN_TYPE = "ALLOW_RESULT_RETURN_TYPE" const val INHERIT_MULTIFILE_PARTS = "INHERIT_MULTIFILE_PARTS" +const val SANITIZE_PARENTHESES = "SANITIZE_PARENTHESES" data class CompilerTestLanguageVersionSettings( private val initialLanguageFeatures: Map, @@ -61,10 +62,11 @@ fun parseLanguageVersionSettings(directiveMap: Map): CompilerTes val skipMetadataVersionCheck = AnalysisFlags.skipMetadataVersionCheck to directiveMap.containsKey(SKIP_METADATA_VERSION_CHECK) val allowResultReturnType = AnalysisFlags.allowResultReturnType to directiveMap.containsKey(ALLOW_RESULT_RETURN_TYPE) val inheritMultifileParts = JvmAnalysisFlags.inheritMultifileParts to directiveMap.containsKey(INHERIT_MULTIFILE_PARTS) + val sanitizeParentheses = JvmAnalysisFlags.sanitizeParentheses to directiveMap.containsKey(SANITIZE_PARENTHESES) if (apiVersionString == null && languageFeaturesString == null && experimental == null && useExperimental == null && !ignoreDataFlowInAssert.second && enableJvmDefault == null && !skipMetadataVersionCheck.second && !allowResultReturnType.second && - !inheritMultifileParts.second + !inheritMultifileParts.second && !sanitizeParentheses.second ) { return null } @@ -81,7 +83,7 @@ fun parseLanguageVersionSettings(directiveMap: Map): CompilerTes mapOf( *listOfNotNull( experimental, useExperimental, enableJvmDefault, ignoreDataFlowInAssert, skipMetadataVersionCheck, allowResultReturnType, - inheritMultifileParts + inheritMultifileParts, sanitizeParentheses ).toTypedArray() ) ) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index cea2b9b396b..ad4e2ebc715 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -15143,6 +15143,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/mangling/noOverrideWithJava.kt"); } + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/box/mangling/parentheses.kt"); + } + @TestMetadata("publicOverride.kt") public void testPublicOverride() throws Exception { runTest("compiler/testData/codegen/box/mangling/publicOverride.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java index 712038c0417..dbd26c98f70 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BytecodeTextTestGenerated.java @@ -2726,6 +2726,29 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/mangling") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Mangling extends AbstractBytecodeTextTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath); + } + + public void testAllFilesPresentInMangling() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/mangling"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/mangling/parentheses.kt"); + } + + @TestMetadata("parenthesesNoSanitize.kt") + public void testParenthesesNoSanitize() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/mangling/parenthesesNoSanitize.kt"); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/multifileClasses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java index 86e21b1219a..42a57c372be 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/IrBytecodeTextTestGenerated.java @@ -2726,6 +2726,29 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest { } } + @TestMetadata("compiler/testData/codegen/bytecodeText/mangling") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Mangling extends AbstractIrBytecodeTextTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath); + } + + public void testAllFilesPresentInMangling() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/mangling"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true); + } + + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/mangling/parentheses.kt"); + } + + @TestMetadata("parenthesesNoSanitize.kt") + public void testParenthesesNoSanitize() throws Exception { + runTest("compiler/testData/codegen/bytecodeText/mangling/parenthesesNoSanitize.kt"); + } + } + @TestMetadata("compiler/testData/codegen/bytecodeText/multifileClasses") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 5f437ab6581..dfc18f341bd 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -15143,6 +15143,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/mangling/noOverrideWithJava.kt"); } + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/box/mangling/parentheses.kt"); + } + @TestMetadata("publicOverride.kt") public void testPublicOverride() throws Exception { runTest("compiler/testData/codegen/box/mangling/publicOverride.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 80565326a0f..29fe116dad1 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -15148,6 +15148,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/mangling/noOverrideWithJava.kt"); } + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/box/mangling/parentheses.kt"); + } + @TestMetadata("publicOverride.kt") public void testPublicOverride() throws Exception { runTest("compiler/testData/codegen/box/mangling/publicOverride.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java index 363e6bd3de3..8f00e1be642 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/IrJsCodegenBoxTestGenerated.java @@ -11663,6 +11663,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest { runTest("compiler/testData/codegen/box/mangling/internalOverrideSuperCall.kt"); } + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/box/mangling/parentheses.kt"); + } + @TestMetadata("publicOverride.kt") public void testPublicOverride() throws Exception { runTest("compiler/testData/codegen/box/mangling/publicOverride.kt"); diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index e897b842e4f..dee6788c1cb 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -12753,6 +12753,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/mangling/internalOverrideSuperCall.kt"); } + @TestMetadata("parentheses.kt") + public void testParentheses() throws Exception { + runTest("compiler/testData/codegen/box/mangling/parentheses.kt"); + } + @TestMetadata("publicOverride.kt") public void testPublicOverride() throws Exception { runTest("compiler/testData/codegen/box/mangling/publicOverride.kt");