Generate parameters metadata for java 8 reflection
This commit is contained in:
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
+4
@@ -72,6 +72,10 @@ public class K2JVMCompilerArguments extends CommonCompilerArguments {
|
||||
@ValueDescription("<version>")
|
||||
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;
|
||||
|
||||
@@ -139,6 +139,8 @@ class K2JVMCompiler : CLICompiler<K2JVMCompilerArguments>() {
|
||||
}
|
||||
}
|
||||
|
||||
configuration.put(JVMConfigurationKeys.PARAMETERS_METADATA, arguments.javaParameters)
|
||||
|
||||
if (arguments.interfaceCompatibility) {
|
||||
val target = configuration.get(JVMConfigurationKeys.JVM_TARGET)
|
||||
if (target != JvmTarget.JVM_1_8) {
|
||||
|
||||
@@ -79,6 +79,9 @@ public class JVMConfigurationKeys {
|
||||
public static final CompilerConfigurationKey<JvmTarget> JVM_TARGET =
|
||||
CompilerConfigurationKey.create("JVM bytecode target version");
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> PARAMETERS_METADATA =
|
||||
CompilerConfigurationKey.create("Parameters metadata for java 1.8 reflection");
|
||||
|
||||
public static final CompilerConfigurationKey<Boolean> INTERFACE_COMPATIBILITY =
|
||||
CompilerConfigurationKey.create("Generate additional 'DefaultImpls' class files for jvm 8 target for compatibility with 6 target interfaces");
|
||||
|
||||
|
||||
Vendored
+1
@@ -14,6 +14,7 @@ where possible options include:
|
||||
-kotlin-home <path> Path to Kotlin compiler home directory, used for runtime libraries discovery
|
||||
-module-name Module name
|
||||
-jvm-target <version> 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 <version> Provide source compatibility with specified language version
|
||||
-api-version <version> Allow to use declarations only from the specified version of bundled libraries
|
||||
-nowarn Generate no warnings
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ where possible options include:
|
||||
-kotlin-home <path> Path to Kotlin compiler home directory, used for runtime libraries discovery
|
||||
-module-name Module name
|
||||
-jvm-target <version> 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 <version> Provide source compatibility with specified language version
|
||||
-api-version <version> Allow to use declarations only from the specified version of bundled libraries
|
||||
-nowarn Generate no warnings
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+39
@@ -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)
|
||||
|
||||
+6
@@ -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
|
||||
|
||||
+7
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user