diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java index e7805d49baf..88a4147a173 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AsmUtil.java @@ -92,6 +92,7 @@ public class AsmUtil { .put(JavaVisibilities.PACKAGE_VISIBILITY, NO_FLAG_PACKAGE_PRIVATE) .build(); + public static final String RECEIVER_NAME = "$receiver"; public static final String CAPTURED_RECEIVER_FIELD = "receiver$0"; public static final String CAPTURED_THIS_FIELD = "this$0"; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 510ac53a431..6b23f3fc40f 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -196,7 +196,9 @@ public class FunctionCodegen { generateMethodAnnotations(functionDescriptor, asmMethod, mv); - generateParameterAnnotations(functionDescriptor, mv, typeMapper.mapSignatureSkipGeneric(functionDescriptor)); + JvmMethodSignature signature = typeMapper.mapSignatureSkipGeneric(functionDescriptor); + generateParameterAnnotations(functionDescriptor, mv, signature); + GenerateJava8ParameterNamesKt.generateParameterNames(functionDescriptor, mv, signature, state, (flags & ACC_SYNTHETIC) != 0); generateBridges(functionDescriptor); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/generateJava8ParameterNames.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/generateJava8ParameterNames.kt new file mode 100644 index 00000000000..93d405e6f1b --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/generateJava8ParameterNames.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen + +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes + +internal fun generateParameterNames( + functionDescriptor: FunctionDescriptor, + mv: MethodVisitor, + jvmSignature: JvmMethodSignature, + state: GenerationState, + isSynthetic: Boolean +) { + if (!state.generateParametersMetadata || isSynthetic) { + return + } + + val iterator = functionDescriptor.valueParameters.iterator() + val kotlinParameterTypes = jvmSignature.valueParameters + var isEnumName = true + + for ((index, parameterSignature) in kotlinParameterTypes.withIndex()) { + val kind = parameterSignature.kind + + val name = when (kind) { + JvmMethodParameterKind.ENUM_NAME_OR_ORDINAL -> { + isEnumName = !isEnumName + if (!isEnumName) "\$enum\$name" else "\$enum\$ordinal" + } + JvmMethodParameterKind.RECEIVER -> AsmUtil.RECEIVER_NAME + JvmMethodParameterKind.OUTER -> AsmUtil.CAPTURED_THIS_FIELD + JvmMethodParameterKind.VALUE -> iterator.next().name.asString() + + JvmMethodParameterKind.CONSTRUCTOR_MARKER, + JvmMethodParameterKind.SUPER_CALL_PARAM, + JvmMethodParameterKind.CAPTURED_LOCAL_VARIABLE, + JvmMethodParameterKind.THIS -> { + //we can't generate null name cause of jdk problem #9045294 + "arg" + index + } + } + + //A construct emitted by a Java compiler must be marked as synthetic if it does not correspond to a construct declared explicitly or + // implicitly in source code, unless the emitted construct is a class initialization method (JVMS §2.9). + //A construct emitted by a Java compiler must be marked as mandated if it corresponds to a formal parameter + // declared implicitly in source code (§8.8.1, §8.8.9, §8.9.3, §15.9.5.1). + val access = when (kind) { + JvmMethodParameterKind.ENUM_NAME_OR_ORDINAL -> Opcodes.ACC_SYNTHETIC + JvmMethodParameterKind.RECEIVER -> Opcodes.ACC_MANDATED + JvmMethodParameterKind.OUTER -> Opcodes.ACC_MANDATED + JvmMethodParameterKind.VALUE -> 0 + + JvmMethodParameterKind.CONSTRUCTOR_MARKER, + JvmMethodParameterKind.SUPER_CALL_PARAM, + JvmMethodParameterKind.CAPTURED_LOCAL_VARIABLE, + JvmMethodParameterKind.THIS -> Opcodes.ACC_SYNTHETIC + } + + mv.visitParameter(name, access) + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index f4d805c5a8b..b2d0e909928 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -167,6 +167,8 @@ class GenerationState @JvmOverloads constructor( val classFileVersion: Int = if (isJvm8Target) Opcodes.V1_8 else Opcodes.V1_6 + val generateParametersMetadata: Boolean = configuration.getBoolean(JVMConfigurationKeys.PARAMETERS_METADATA) + init { this.interceptedBuilderFactory = builderFactory .wrapWith( diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java index 644f3f0f4be..b8a266b9645 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.java @@ -72,6 +72,10 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments { @ValueDescription("") public String jvmTarget; + @GradleOption(DefaultValues.BooleanFalseDefault.class) + @Argument(value = "java-parameters", description = "Generate metadata for Java 1.8 reflection on method parameters") + public boolean javaParameters; + // Advanced options @Argument(value = "Xno-call-assertions", description = "Don't generate not-null assertion after each invocation of method returning not-null") public boolean noCallAssertions; diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index 45aedbf353c..52e1929c31f 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -139,6 +139,8 @@ class K2JVMCompiler : CLICompiler() { } } + configuration.put(JVMConfigurationKeys.PARAMETERS_METADATA, arguments.javaParameters) + if (arguments.interfaceCompatibility) { val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) if (target != JvmTarget.JVM_1_8) { diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 8b55a2b0549..5998cc60474 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -79,6 +79,9 @@ public class JVMConfigurationKeys { public static final CompilerConfigurationKey JVM_TARGET = CompilerConfigurationKey.create("JVM bytecode target version"); + public static final CompilerConfigurationKey PARAMETERS_METADATA = + CompilerConfigurationKey.create("Parameters metadata for java 1.8 reflection"); + public static final CompilerConfigurationKey INTERFACE_COMPATIBILITY = CompilerConfigurationKey.create("Generate additional 'DefaultImpls' class files for jvm 8 target for compatibility with 6 target interfaces"); diff --git a/compiler/testData/cli/jvm/help.out b/compiler/testData/cli/jvm/help.out index 66e5cd9ff39..4d1710521ad 100644 --- a/compiler/testData/cli/jvm/help.out +++ b/compiler/testData/cli/jvm/help.out @@ -14,6 +14,7 @@ where possible options include: -kotlin-home Path to Kotlin compiler home directory, used for runtime libraries discovery -module-name Module name -jvm-target Target version of the generated JVM bytecode (1.6 or 1.8), default is 1.6 + -java-parameters Generate metadata for Java 1.8 reflection on method parameters -language-version Provide source compatibility with specified language version -api-version Allow to use declarations only from the specified version of bundled libraries -nowarn Generate no warnings diff --git a/compiler/testData/cli/jvm/wrongArgument.out b/compiler/testData/cli/jvm/wrongArgument.out index 35d77484a70..f7bdd44574a 100644 --- a/compiler/testData/cli/jvm/wrongArgument.out +++ b/compiler/testData/cli/jvm/wrongArgument.out @@ -15,6 +15,7 @@ where possible options include: -kotlin-home Path to Kotlin compiler home directory, used for runtime libraries discovery -module-name Module name -jvm-target Target version of the generated JVM bytecode (1.6 or 1.8), default is 1.6 + -java-parameters Generate metadata for Java 1.8 reflection on method parameters -language-version Provide source compatibility with specified language version -api-version Allow to use declarations only from the specified version of bundled libraries -nowarn Generate no warnings diff --git a/compiler/testData/codegen/java8/box/parametersMetadata/enum.kt b/compiler/testData/codegen/java8/box/parametersMetadata/enum.kt new file mode 100644 index 00000000000..6264f246fb5 --- /dev/null +++ b/compiler/testData/codegen/java8/box/parametersMetadata/enum.kt @@ -0,0 +1,22 @@ +// WITH_RUNTIME +// FULL_JDK +// KOTLIN_CONFIGURATION_FLAGS: +JVM.PARAMETERS_METADATA + +enum class A(val OK: String) { + +} + +fun box(): String { + val clazz = A::class.java + val constructor = clazz.getDeclaredConstructor(String::class.java, Int::class.java, String::class.java) + val parameters = constructor.getParameters() + + if (parameters[0].name != "\$enum\$name") return "wrong entry name: ${parameters[0].name}" + if (!parameters[0].isSynthetic() || parameters[0].isImplicit()) return "wrong name flags: ${parameters[0].modifiers}" + + if (parameters[1].name != "\$enum\$ordinal") return "wrong ordinal name: ${parameters[1].name}" + if (!parameters[1].isSynthetic() || parameters[1].isImplicit()) return "wrong ordinal flags: ${parameters[1].modifiers}" + + if (parameters[2].modifiers != 0) return "wrong modifier on value parameter: ${parameters[2].modifiers}" + return parameters[2].name +} \ No newline at end of file diff --git a/compiler/testData/codegen/java8/box/parametersMetadata/extensionFunction.kt b/compiler/testData/codegen/java8/box/parametersMetadata/extensionFunction.kt new file mode 100644 index 00000000000..37b087a31d4 --- /dev/null +++ b/compiler/testData/codegen/java8/box/parametersMetadata/extensionFunction.kt @@ -0,0 +1,19 @@ +// WITH_RUNTIME +// FULL_JDK +// KOTLIN_CONFIGURATION_FLAGS: +JVM.PARAMETERS_METADATA + +class A() { + fun String.test(OK: String) { + + } +} + +fun box(): String { + val clazz = A::class.java + val method = clazz.getDeclaredMethod("test", String::class.java, String::class.java) + val parameters = method.getParameters() + + if (!parameters[0].isImplicit() || parameters[0].isSynthetic()) return "wrong modifier on receiver parameter: ${parameters[0].modifiers}" + + return parameters[1].name +} \ No newline at end of file diff --git a/compiler/testData/codegen/java8/box/parametersMetadata/function.kt b/compiler/testData/codegen/java8/box/parametersMetadata/function.kt new file mode 100644 index 00000000000..35ca1a140a1 --- /dev/null +++ b/compiler/testData/codegen/java8/box/parametersMetadata/function.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// FULL_JDK +// KOTLIN_CONFIGURATION_FLAGS: +JVM.PARAMETERS_METADATA + +class A() { + fun test(OK: String) { + + } +} + +fun box(): String { + val clazz = A::class.java + val method = clazz.getDeclaredMethod("test", String::class.java) + val parameters = method.getParameters() + + if (parameters[0].modifiers != 0) return "wrong modifier on value parameter: ${parameters[0].modifiers}" + return parameters[0].name +} \ No newline at end of file diff --git a/compiler/testData/codegen/java8/box/parametersMetadata/innerClass.kt b/compiler/testData/codegen/java8/box/parametersMetadata/innerClass.kt new file mode 100644 index 00000000000..5b35fbf756b --- /dev/null +++ b/compiler/testData/codegen/java8/box/parametersMetadata/innerClass.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// FULL_JDK +// KOTLIN_CONFIGURATION_FLAGS: +JVM.PARAMETERS_METADATA + +class A { + inner class B +} + +fun box(): String { + val clazz = A.B::class.java + val constructor = clazz.getDeclaredConstructors().single() + val parameters = constructor.getParameters() + + if (parameters[0].name != "this$0") return "wrong outer name: ${parameters[0].name}" + if (!parameters[0].isImplicit() || parameters[0].isSynthetic()) return "wrong outer flags: ${parameters[0].modifiers}" + + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/java8/box/parametersMetadata/superParams.kt b/compiler/testData/codegen/java8/box/parametersMetadata/superParams.kt new file mode 100644 index 00000000000..933d3155214 --- /dev/null +++ b/compiler/testData/codegen/java8/box/parametersMetadata/superParams.kt @@ -0,0 +1,18 @@ +// WITH_RUNTIME +// FULL_JDK +// KOTLIN_CONFIGURATION_FLAGS: +JVM.PARAMETERS_METADATA + +open class A(val s: String) + +fun test(OK: String) = object : A(OK) { +} + +fun box(): String { + val value = test("OK") + val clazz = value.javaClass + val constructor = clazz.getDeclaredConstructors().single() + val parameters = constructor.getParameters() + + if (!parameters[0].isSynthetic() || parameters[0].isImplicit()) return "wrong modifier on value parameter: ${parameters[0].modifiers}" + return value.s +} \ No newline at end of file diff --git a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java index c70d86ecc81..cc35d7e0a7e 100644 --- a/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java +++ b/compiler/tests-java8/tests/org/jetbrains/kotlin/codegen/BlackBoxWithJava8CodegenTestGenerated.java @@ -309,6 +309,45 @@ public class BlackBoxWithJava8CodegenTestGenerated extends AbstractBlackBoxCodeg } } + @TestMetadata("compiler/testData/codegen/java8/box/parametersMetadata") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ParametersMetadata extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInParametersMetadata() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/java8/box/parametersMetadata"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("enum.kt") + public void testEnum() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/parametersMetadata/enum.kt"); + doTest(fileName); + } + + @TestMetadata("extensionFunction.kt") + public void testExtensionFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/parametersMetadata/extensionFunction.kt"); + doTest(fileName); + } + + @TestMetadata("function.kt") + public void testFunction() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/parametersMetadata/function.kt"); + doTest(fileName); + } + + @TestMetadata("innerClass.kt") + public void testInnerClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/parametersMetadata/innerClass.kt"); + doTest(fileName); + } + + @TestMetadata("superParams.kt") + public void testSuperParams() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/java8/box/parametersMetadata/superParams.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/java8/box/reflection") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptions.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptions.kt index f28b28745c3..d61187baad3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptions.kt @@ -10,6 +10,12 @@ interface KotlinJvmOptions : org.jetbrains.kotlin.gradle.dsl.KotlinCommonOption */ var includeRuntime: kotlin.Boolean + /** + * Generate metadata for Java 1.8 reflection on method parameters + * Default value: false + */ + var javaParameters: kotlin.Boolean + /** * Path to JDK home directory to include into classpath, if differs from default JAVA_HOME * Default value: null diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptionsBase.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptionsBase.kt index ca0a576faf9..27f69a58b68 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptionsBase.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/dsl/KotlinJvmOptionsBase.kt @@ -29,6 +29,11 @@ internal abstract class KotlinJvmOptionsBase : org.jetbrains.kotlin.gradle.dsl.K get() = includeRuntimeField ?: false set(value) { includeRuntimeField = value } + private var javaParametersField: kotlin.Boolean? = null + override var javaParameters: kotlin.Boolean + get() = javaParametersField ?: false + set(value) { javaParametersField = value } + private var jdkHomeField: kotlin.String?? = null override var jdkHome: kotlin.String? get() = jdkHomeField ?: null @@ -60,6 +65,7 @@ internal abstract class KotlinJvmOptionsBase : org.jetbrains.kotlin.gradle.dsl.K suppressWarningsField?.let { args.suppressWarnings = it } verboseField?.let { args.verbose = it } includeRuntimeField?.let { args.includeRuntime = it } + javaParametersField?.let { args.javaParameters = it } jdkHomeField?.let { args.jdkHome = it } jvmTargetField?.let { args.jvmTarget = it } noJdkField?.let { args.noJdk = it } @@ -74,6 +80,7 @@ internal fun org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments.fi suppressWarnings = false verbose = false includeRuntime = false + javaParameters = false jdkHome = null jvmTarget = "1.6" noJdk = false