[JVM] Implement extension point to avoid excess reflective calls
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
index 64440fb..a7d3c59 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/components/KaFirCompilerFacility.kt
@@ -48,6 +48,7 @@
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
import org.jetbrains.kotlin.codegen.state.CompiledCodeProvider
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CommonConfigurationKeys
@@ -118,6 +119,7 @@
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi2ir.generators.fragments.EvaluatorFragmentInfo
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.source.PsiSourceFile
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -127,6 +129,7 @@
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
import org.jetbrains.kotlin.utils.yieldIfNotNull
import java.util.*
+import org.jetbrains.org.objectweb.asm.Type
/**
* A source file to be compiled as a part of some [ChunkToCompile].
@@ -247,7 +250,8 @@
jvmIrDeserializer,
codeFragmentMappings?.takeIf { chunk.hasCodeFragments },
generateClassFilter,
- KaFirDelegatingCompiledCodeProvider(registeredCodeProviders)
+ KaFirDelegatingCompiledCodeProvider(registeredCodeProviders),
+ target.debuggerExtension?.jvmMemberAccessOracle
)
when (result) {
@@ -819,7 +823,8 @@
jvmIrDeserializer: JvmIrDeserializer,
codeFragmentMappings: CodeFragmentMappings?,
generateClassFilter: GenerationState.GenerateClassFilter,
- compiledCodeProvider: CompiledCodeProvider
+ compiledCodeProvider: CompiledCodeProvider,
+ jvmMemberAccessOracle: JvmMemberAccessOracle?,
): KaCompilationResult {
val session = resolutionFacade.sessionProvider.getResolvableSession(module)
val configuration = baseConfiguration.copy().apply {
@@ -877,7 +882,8 @@
configuration = configuration,
isCodeFragment = codeFragmentMappings != null,
irModuleFragment = fir2IrResult.irModuleFragment,
- typeArgumentsMap = convertedMapping
+ typeArgumentsMap = convertedMapping,
+ jvmMemberAccessOracle = jvmMemberAccessOracle
)
val result = runJvmIrCodeGen(
@@ -1270,6 +1276,7 @@
isCodeFragment: Boolean,
irModuleFragment: IrModuleFragment,
typeArgumentsMap: Map<IrTypeParameterSymbol, IrType>,
+ jvmMemberAccessOracle: JvmMemberAccessOracle?,
): JvmIrCodegenFactory {
val jvmGeneratorExtensions = object : JvmGeneratorExtensionsImpl(configuration) {
override fun getContainerSource(descriptor: DeclarationDescriptor): DeserializedContainerSource? {
@@ -1310,6 +1317,17 @@
jvmGeneratorExtensions = jvmGeneratorExtensions,
evaluatorFragmentInfoForPsi2Ir = evaluatorFragmentInfoForPsi2Ir,
ideCodegenSettings = ideCodegenSettings,
+ jvmMemberAccessOracle = if (jvmMemberAccessOracle != null) {
+ object : JvmMemberAccessOracleBE {
+ override fun tryMakeFieldAccessible(declaringClass: Type, name: String) =
+ jvmMemberAccessOracle.tryMakeFieldAccessible(declaringClass, name)
+
+ override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) =
+ jvmMemberAccessOracle.tryMakeMethodAccessible(declaringClass, signature)
+ }
+ } else {
+ null
+ }
)
}
}
diff --git a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
index 20ec96b..1d84d91 100644
--- a/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
+++ b/analysis/analysis-api-impl-base/tests/org/jetbrains/kotlin/analysis/api/impl/base/test/cases/components/compilerFacility/AbstractCompilerFacilityTest.kt
@@ -22,6 +22,7 @@
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.codegen.BytecodeListingTextCollectingVisitor
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
@@ -141,7 +142,7 @@
val target = KaCompilerTarget.Jvm(
isTestMode = true,
compiledClassHandler = null,
- debuggerExtension = DebuggerExtension(callStack.asSequence())
+ debuggerExtension = DebuggerExtension(callStack.asSequence(), JvmMemberAccessOracle.EMPTY)
)
val allowedErrorFilter: (KaDiagnostic) -> Boolean = { it.factoryName in ALLOWED_ERRORS }
diff --git a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
index f70d167..2fae1cf 100644
--- a/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
+++ b/analysis/analysis-api/src/org/jetbrains/kotlin/analysis/api/components/KaCompilerFacility.kt
@@ -15,6 +15,8 @@
import org.jetbrains.kotlin.config.CompilerConfigurationKey
import org.jetbrains.kotlin.psi.KtCodeFragment
import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
+import org.jetbrains.org.objectweb.asm.Type
import java.io.File
/**
@@ -179,6 +181,29 @@
*
* @property stack A sequence of PSI elements of the expressions (function calls or property accesses) in the current execution stack,
* listed from the top to the bottom.
+ *
+ * @property jvmMemberAccessOracle is a compiler extension point using which debugger can avoid generation of reflective calls for private
+ * members
*/
@KaExperimentalApi
-public class DebuggerExtension(public val stack: Sequence<PsiElement?>)
\ No newline at end of file
+public class DebuggerExtension(
+ public val stack: Sequence<PsiElement?>,
+ public val jvmMemberAccessOracle: JvmMemberAccessOracle,
+)
+
+@KaExperimentalApi
+public interface JvmMemberAccessOracle {
+ public fun tryMakeFieldAccessible(declaringClass: Type, name: String): Boolean
+ public fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature): Boolean
+
+ public companion object {
+ public val EMPTY: JvmMemberAccessOracle = object : JvmMemberAccessOracle {
+ override fun tryMakeFieldAccessible(declaringClass: Type, name: String) = false
+ override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) = false
+ }
+ public val ALWAYS_SAY_YES: JvmMemberAccessOracle = object : JvmMemberAccessOracle {
+ override fun tryMakeFieldAccessible(declaringClass: Type, name: String) = true
+ override fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature) = true
+ }
+ }
+}
\ No newline at end of file
diff --git a/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt
new file mode 100644
index 0000000..23fcdd0
--- /dev/null
+++ b/compiler/backend.common.jvm/src/org/jetbrains/kotlin/codegen/JvmMemberAccessOracleBE.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2010-2025 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
+ */
+
+package org.jetbrains.kotlin.codegen
+
+import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
+import org.jetbrains.org.objectweb.asm.Type
+
+interface JvmMemberAccessOracleBE {
+ fun tryMakeFieldAccessible(declaringClass: Type, name: String): Boolean
+ fun tryMakeMethodAccessible(declaringClass: Type, signature: JvmMethodSignature): Boolean
+}
\ No newline at end of file
diff --git a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
index ee62a75..f29ebdf 100644
--- a/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
+++ b/compiler/ir/backend.jvm/entrypoint/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt
@@ -24,6 +24,7 @@
import org.jetbrains.kotlin.backend.jvm.serialization.DisabledIdSignatureDescriptor
import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
import org.jetbrains.kotlin.builtins.StandardNames.BUILT_INS_PACKAGE_FQ_NAMES
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
import org.jetbrains.kotlin.codegen.addCompiledPartsAndSort
import org.jetbrains.kotlin.codegen.loadCompiledModule
import org.jetbrains.kotlin.codegen.state.GenerationState
@@ -75,6 +76,7 @@
private val externalSymbolTable: SymbolTable? = null,
private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
+ private val jvmMemberAccessOracle: JvmMemberAccessOracleBE? = null,
private val ideCodegenSettings: IdeCodegenSettings = IdeCodegenSettings(),
) {
/**
@@ -325,8 +327,7 @@
}
fun invokeLowerings(state: GenerationState, input: BackendInput): CodegenInput {
- val (irModuleFragment, irBuiltIns, symbolTable, irProviders, extensions, backendExtension, irPluginContext) =
- input
+ val (irModuleFragment, irBuiltIns, symbolTable, irProviders, extensions, backendExtension, irPluginContext) = input
val irSerializer = if (
state.configuration.get(JVMConfigurationKeys.SERIALIZE_IR, JvmSerializeIrMode.NONE) != JvmSerializeIrMode.NONE
)
@@ -338,7 +339,12 @@
)
if (evaluatorFragmentInfoForPsi2Ir != null) {
context.evaluatorData =
- JvmEvaluatorData(mutableMapOf(), evaluatorFragmentInfoForPsi2Ir.methodIR, evaluatorFragmentInfoForPsi2Ir.typeArgumentsMap)
+ JvmEvaluatorData(
+ mutableMapOf(),
+ evaluatorFragmentInfoForPsi2Ir.methodIR,
+ evaluatorFragmentInfoForPsi2Ir.typeArgumentsMap,
+ jvmMemberAccessOracle
+ )
}
val generationExtensions = state.project.filteredExtensions
.mapNotNull { it.getPlatformIntrinsicExtension(context) as? JvmIrIntrinsicExtension }
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
index 6f464fb..a1e2e32 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SpecialAccess.kt
@@ -17,6 +17,7 @@
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
+import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrParameterKind
@@ -24,18 +25,24 @@
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
+import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl
import org.jetbrains.kotlin.ir.overrides.isNonPrivate
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
+import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
+import org.jetbrains.kotlin.ir.symbols.impl.IrFieldSymbolImpl
+import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.kotlin.FacadeClassSource
+import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
+import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.org.objectweb.asm.Type
-import org.jetbrains.org.objectweb.asm.commons.Method
/**
* This lowering replaces member accesses that are illegal according to JVM accessibility rules with corresponding calls to the
@@ -66,6 +73,7 @@
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
private lateinit var inlineScopeResolver: IrInlineScopeResolver
+ private val jvmMemberAccessOracle = context?.evaluatorData?.jvmMemberAccessOracle
override fun lower(irFile: IrFile) {
if (context.evaluatorData == null) return
@@ -111,11 +119,11 @@
}
return when {
- expression.symbol.owner.isGetter -> generateReflectiveAccessForGetter(expression)
- expression.symbol.owner.isSetter -> generateReflectiveAccessForSetter(expression)
- expression.dispatchReceiver == null -> generateReflectiveStaticCall(expression)
+ expression.symbol.owner.isGetter -> generateSuitableAccessForGetter(expression)
+ expression.symbol.owner.isSetter -> generateSuitableAccessForSetter(expression)
+ expression.dispatchReceiver == null -> generateSuitableStaticCall(expression)
superQualifier != null -> generateInvokeSpecialForCall(expression, superQualifier)
- else -> generateReflectiveMethodInvocation(expression)
+ else -> generateSuitableMethodInvocation(expression)
}
}
@@ -126,7 +134,7 @@
return if (field.isAccessible()) {
expression
} else {
- generateReflectiveFieldGet(expression)
+ generateSuitableFieldGet(expression)
}
}
@@ -139,7 +147,7 @@
} else if (field.owner.correspondingPropertySymbol?.owner?.isConst == true || (field.owner.isFromJava() && field.owner.isFinal)) {
generateThrowIllegalAccessException(expression)
} else {
- generateReflectiveFieldSet(expression)
+ generateSuitableFieldSet(expression)
}
}
@@ -150,7 +158,7 @@
return if (callee.isAccessible()) {
expression
} else {
- generateReflectiveConstructorInvocation(expression)
+ generateReflectiveConstructorInvocationOrNullIfAccessible(expression) ?: expression
}
}
@@ -161,7 +169,7 @@
return if (callee.isAccessible()) {
expression
} else {
- generateReflectiveAccessForCompanion(expression)
+ generateSuitableAccessForCompanion(expression)
}
}
@@ -247,7 +255,7 @@
private fun IrBuilderWithScope.methodInvoke(
method: IrExpression,
receiver: IrExpression,
- arguments: List<IrExpression>
+ arguments: List<IrExpression>,
): IrExpression =
irCall(reflectSymbols.javaLangReflectMethodInvoke).apply {
this.arguments[0] = method
@@ -277,34 +285,54 @@
this.arguments[1] = irVararg(context.irBuiltIns.anyNType, arguments.map { coerceToUnboxed(it) })
}
+ private val IrType.asmType: Type get() = context.defaultTypeMapper.mapType(this, TypeMappingMode.CLASS_DECLARATION)
+
/**
* Specific reflective "patches"
*/
- private fun generateReflectiveMethodInvocation(
+ private fun redirectCallIfPrivateMultifileMember(call: IrCall, functionName: String) {
+ val declaringClass = getDeclaredClassType(call)
+ val function = call.symbol.owner
+ val classPartStubOrThis = declaringClass.classPartForMultifileFacadeOrThis
+ if (classPartStubOrThis != declaringClass && DescriptorVisibilities.isPrivate(call.symbol.owner.visibility)) {
+ val newSymbol = IrFactoryImpl.createSimpleFunction(
+ UNDEFINED_OFFSET,
+ UNDEFINED_OFFSET,
+ IrDeclarationOrigin.SYNTHETIC_ACCESSOR,
+ Name.identifier("$functionName$${classPartStubOrThis.classOrFail.owner.name}"),
+ DescriptorVisibilities.PRIVATE,
+ function.isInline,
+ function.isExpect,
+ function.returnType,
+ function.modality,
+ IrSimpleFunctionSymbolImpl(),
+ function.isTailrec,
+ function.isSuspend,
+ function.isOperator,
+ function.isInfix
+ ).apply {
+ parent = classPartStubOrThis.classOrFail.owner
+ }.symbol
+ call.symbol = newSymbol
+ }
+ }
+
+ private fun generateReflectiveMethodInvocationOrNullIfAccessible(
declaringClass: IrType,
signature: JvmMethodSignature,
receiver: IrExpression?, // null => static method on `declaringClass`
arguments: List<IrExpression>,
returnType: IrType,
symbol: IrSimpleFunctionSymbol,
- ): IrExpression {
- val classPartStubOrThis = declaringClass.classPartForMultifileFacadeOrThis
- val signatureToLookup = if (classPartStubOrThis != declaringClass && DescriptorVisibilities.isPrivate(symbol.owner.visibility)) {
- JvmMethodSignature(
- Method(
- "${signature.asmMethod.name}$${classPartStubOrThis.classOrFail.owner.name}",
- signature.asmMethod.descriptor
- ),
- signature.parameters
- )
- } else {
- signature
+ ): IrExpression? {
+ if (jvmMemberAccessOracle?.tryMakeMethodAccessible(declaringClass.asmType, signature) == true) {
+ return null
}
return context.createJvmIrBuilder(symbol).irBlock(resultType = returnType) {
val methodVar =
createTmpVariable(
- getDeclaredMethod(javaClassObject(classPartStubOrThis), signatureToLookup),
+ getDeclaredMethod(javaClassObject(declaringClass), signature),
nameHint = "method",
irType = reflectSymbols.javaLangReflectMethod.defaultType
)
@@ -323,7 +351,7 @@
arguments[0] = expression
}
- private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression {
+ private fun generateSuitableMethodInvocation(call: IrCall): IrExpression {
val targetFunction = call.symbol.owner
val arguments = (targetFunction.parameters zip call.arguments).mapNotNull { (param, arg) ->
when {
@@ -332,37 +360,44 @@
else -> null
}
}
-
- return generateReflectiveMethodInvocation(
+ redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(targetFunction))
+ val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(targetFunction)
+ return generateReflectiveMethodInvocationOrNullIfAccessible(
getDeclaredClassType(call),
- context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(targetFunction),
+ signature,
call.dispatchReceiver,
arguments,
call.type,
call.symbol
- )
+ ) ?: call
}
- private fun generateReflectiveStaticCall(call: IrCall): IrExpression {
+ private fun generateSuitableStaticCall(call: IrCall): IrExpression {
assert(call.dispatchReceiver == null) { "Assumed-to-be static call with a dispatch receiver" }
- return generateReflectiveMethodInvocation(
+ redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(call.symbol.owner))
+ val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+ return generateReflectiveMethodInvocationOrNullIfAccessible(
call.symbol.owner.parentAsClass.defaultType,
- context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner),
+ signature,
null, // static call
call.nonDispatchArguments.filterNotNull(),
call.type,
call.symbol
- )
+ ) ?: call
}
- private fun generateReflectiveConstructorInvocation(call: IrConstructorCall): IrExpression =
- context.createJvmIrBuilder(call.symbol)
+ private fun generateReflectiveConstructorInvocationOrNullIfAccessible(call: IrConstructorCall): IrExpression? {
+ val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+ if (jvmMemberAccessOracle?.tryMakeMethodAccessible(call.symbol.owner.parentAsClass.defaultType.asmType, signature) == true) {
+ return null
+ }
+ return context.createJvmIrBuilder(call.symbol)
.irBlock(resultType = call.type) {
val constructorVar =
createTmpVariable(
getDeclaredConstructor(
javaClassObject(call.symbol.owner.parentAsClass.defaultType),
- this@SpecialAccessLowering.context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(call.symbol.owner)
+ signature
),
nameHint = "constructor",
irType = reflectSymbols.javaLangReflectConstructor.defaultType
@@ -370,18 +405,20 @@
+constructorSetAccessible(irGet(constructorVar))
+constructorNewInstance(irGet(constructorVar), call.nonDispatchArguments.filterNotNull())
}
+ }
- private fun generateReflectiveFieldGet(
+ private fun generateReflectiveFieldGetOrNullIfAccessible(
declaringClass: IrType,
fieldName: String,
fieldType: IrType,
instance: IrExpression?, // null ==> static field on `declaringClass`
symbol: IrSymbol,
- ): IrExpression =
- context.createJvmIrBuilder(symbol)
+ ): IrExpression? {
+ if (jvmMemberAccessOracle?.tryMakeFieldAccessible(declaringClass.asmType, fieldName) == true) return null
+ return context.createJvmIrBuilder(symbol)
.irBlock(resultType = fieldType) {
val classVar = createTmpVariable(
- javaClassObject(declaringClass.classPartForMultifileFacadeOrThis),
+ javaClassObject(declaringClass),
nameHint = "klass",
irType = symbols.kClassJavaPropertyGetter.returnType
)
@@ -393,6 +430,7 @@
+fieldSetAccessible(irGet(fieldVar))
+coerceResult(fieldGet(irGet(fieldVar), instance ?: irGet(classVar)), fieldType)
}
+ }
private val IrType.classPartForMultifileFacadeOrThis: IrType
get() {
@@ -420,29 +458,30 @@
}
- private fun generateReflectiveFieldGet(getField: IrGetField): IrExpression =
- generateReflectiveFieldGet(
+ private fun generateSuitableFieldGet(getField: IrGetField): IrExpression =
+ generateReflectiveFieldGetOrNullIfAccessible(
getField.symbol.owner.parentClassOrNull!!.defaultType,
getField.symbol.owner.name.asString(),
getField.type,
getField.receiver,
getField.symbol
- )
+ ) ?: getField
- private fun generateReflectiveFieldSet(
+ private fun generateSuitableFieldSet(
declaringClass: IrType,
fieldName: String,
value: IrExpression,
type: IrType,
instance: IrExpression?,
- symbol: IrSymbol
- ): IrExpression {
+ symbol: IrSymbol,
+ ): IrExpression? {
+ if (jvmMemberAccessOracle?.tryMakeFieldAccessible(declaringClass.asmType, fieldName) == true) return null
return context.createJvmIrBuilder(symbol)
.irBlock(resultType = type) {
val fieldVar =
createTmpVariable(
getDeclaredField(
- javaClassObject(declaringClass.classPartForMultifileFacadeOrThis),
+ javaClassObject(declaringClass),
fieldName
),
nameHint = "field",
@@ -453,15 +492,15 @@
}
}
- private fun generateReflectiveFieldSet(setField: IrSetField): IrExpression =
- generateReflectiveFieldSet(
+ private fun generateSuitableFieldSet(setField: IrSetField): IrExpression =
+ generateSuitableFieldSet(
setField.symbol.owner.parentClassOrNull!!.defaultType,
setField.symbol.owner.name.asString(),
setField.value,
setField.type,
setField.receiver,
setField.symbol,
- )
+ ) ?: setField
private fun isPresentInBytecode(accessor: IrSimpleFunction): Boolean {
val property = accessor.correspondingPropertySymbol!!.owner
@@ -483,63 +522,97 @@
callsOnCompanionObjects[call]?.let {
val parentAsClass = it.owner.parentAsClass
if (!parentAsClass.isJvmInterface) {
- return parentAsClass.defaultType to null
+ return parentAsClass.defaultType.classPartForMultifileFacadeOrThis to null
}
}
val type = getDeclaredClassType(call)
- return type to call.dispatchReceiver
+ return type.classPartForMultifileFacadeOrThis to call.dispatchReceiver
}
private fun getDeclaredClassType(call: IrCall) =
call.superQualifierSymbol?.defaultType ?: call.symbol.owner.resolveFakeOverrideOrFail().parentAsClass.defaultType
- private fun generateReflectiveAccessForGetter(call: IrCall): IrExpression {
+ private fun generateSuitableAccessForGetter(call: IrCall): IrExpression {
val realGetter = call.symbol.owner.resolveFakeOverrideOrFail()
if (isPresentInBytecode(realGetter)) {
- return generateReflectiveMethodInvocation(
+ redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(realGetter))
+ val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realGetter)
+ return generateReflectiveMethodInvocationOrNullIfAccessible(
realGetter.parentAsClass.defaultType,
- context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realGetter),
+ signature,
call.dispatchReceiver,
call.nonDispatchArguments.filterNotNull(),
realGetter.returnType,
realGetter.symbol
- )
+ ) ?: call
}
-
val (fieldLocation, instance) = fieldLocationAndReceiver(call)
- return generateReflectiveFieldGet(
+ return generateReflectiveFieldGetOrNullIfAccessible(
fieldLocation,
realGetter.correspondingPropertySymbol!!.owner.name.asString(),
realGetter.returnType,
instance,
call.symbol,
+ ) ?: IrGetFieldImpl(
+ call.startOffset,
+ call.endOffset,
+ getOrCreateSymbolForField(instance, fieldLocation, realGetter),
+ realGetter.returnType,
+ instance
)
}
- private fun generateReflectiveAccessForSetter(call: IrCall): IrExpression {
+ private fun getOrCreateSymbolForField(accessReceiver: IrExpression?, fieldLocation: IrType, accessor: IrSimpleFunction): IrFieldSymbol {
+ val field = accessor.correspondingPropertySymbol!!.owner.backingField!!
+ if (field.parent as IrClass? == fieldLocation.classOrNull) return field.symbol
+ return IrFactoryImpl.createField(
+ UNDEFINED_OFFSET, UNDEFINED_OFFSET,
+ IrDeclarationOrigin.SYNTHETIC_ACCESSOR,
+ field.name,
+ DescriptorVisibilities.PRIVATE,
+ IrFieldSymbolImpl(),
+ field.type,
+ field.isFinal,
+ accessReceiver == null,
+ field.isExternal
+ ).apply {
+ parent = fieldLocation.classOrFail.owner
+ }.symbol
+ }
+
+ private fun generateSuitableAccessForSetter(call: IrCall): IrExpression {
val realSetter = call.symbol.owner.resolveFakeOverrideOrFail()
if (isPresentInBytecode(realSetter)) {
- return generateReflectiveMethodInvocation(
+ redirectCallIfPrivateMultifileMember(call, context.defaultMethodSignatureMapper.mapFunctionName(realSetter))
+ val signature = context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realSetter)
+ return generateReflectiveMethodInvocationOrNullIfAccessible(
realSetter.parentAsClass.defaultType,
- context.defaultMethodSignatureMapper.mapSignatureSkipGeneric(realSetter),
+ signature,
call.dispatchReceiver,
call.nonDispatchArguments.filterNotNull(),
realSetter.returnType,
call.symbol
- )
+ ) ?: call
}
val (fieldLocation, receiver) = fieldLocationAndReceiver(call)
- return generateReflectiveFieldSet(
+ return generateSuitableFieldSet(
fieldLocation,
realSetter.correspondingPropertySymbol!!.owner.name.asString(),
call.arguments.last()!!,
call.type,
receiver,
call.symbol
+ ) ?: IrSetFieldImpl(
+ call.startOffset,
+ call.endOffset,
+ getOrCreateSymbolForField(receiver, fieldLocation, realSetter),
+ receiver,
+ call.arguments.last()!!,
+ call.type,
)
}
@@ -583,12 +656,12 @@
}
}
- private fun generateReflectiveAccessForCompanion(call: IrGetObjectValue): IrExpression =
- generateReflectiveFieldGet(
+ private fun generateSuitableAccessForCompanion(call: IrGetObjectValue): IrExpression =
+ generateReflectiveFieldGetOrNullIfAccessible(
call.symbol.owner.parentAsClass.defaultType,
"Companion",
call.type,
null,
call.symbol
- )
+ ) ?: call
}
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
index e06ca6ad..f9c18d7 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmEvaluatorData.kt
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.backend.jvm
+import org.jetbrains.kotlin.codegen.JvmMemberAccessOracleBE
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.types.IrType
@@ -18,5 +19,7 @@
val evaluatorGeneratedFunction: IrFunction?,
// If the code fragment captures some reified type parameters, we will need the corresponding arguments for the proper codegen
// Bytecode might not contain all the required data, so we extract it from the debugger API and store here
- val capturedTypeParametersMapping: Map<IrTypeParameterSymbol, IrType>
+ val capturedTypeParametersMapping: Map<IrTypeParameterSymbol, IrType>,
+ // `SpecialAccessLowering` uses this to avoid generating of excess reflective accesses to private members
+ val jvmMemberAccessOracle: JvmMemberAccessOracleBE?
)