[EE-IR] Use reflection to avoid the need for accessors when compiling

evaluator fragments

- Refactor JvmIrCodegenFactory to allow for caller's choice of
  lowerings.

- Refactor of SyntheticAccessorLowering to expose the accessibility
  check used to determine the need for accessors.

- Replacement of field, method and constructor uses with corresponding
  java.lang.reflection API via additional lowering running _before_
  the existing IR pipeline.

- Emulating super calls in the context of class methods via new
  compiler intrinsic.

- psi2ir: Add support for `_field` suffix on properties in the
  evaluator for accessing underlying fields when applicable.

- Add `JvmGeneratorExtensions` point for determining whether a
  property accessor has a user-supplied body in order to predict how
  they are ultimately compiled for the JVM.
This commit is contained in:
Kristoffer Andersen
2021-08-26 16:17:30 +02:00
committed by Alexander Udalov
parent 2e535366f1
commit 771b79d045
12 changed files with 789 additions and 64 deletions
@@ -8,8 +8,10 @@ package org.jetbrains.kotlin.backend.jvm
import org.jetbrains.kotlin.analyzer.hasJdkCapability
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl
import org.jetbrains.kotlin.backend.common.phaser.NamedCompilerPhase
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.backend.common.phaser.then
import org.jetbrains.kotlin.backend.jvm.ir.getKtFile
import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor
import org.jetbrains.kotlin.codegen.CodegenFactory
@@ -48,7 +50,8 @@ open class JvmIrCodegenFactory(
private val externalMangler: JvmDescriptorMangler? = null,
private val externalSymbolTable: SymbolTable? = null,
private val jvmGeneratorExtensions: JvmGeneratorExtensionsImpl = JvmGeneratorExtensionsImpl(configuration),
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null
private val prefixPhases: NamedCompilerPhase<JvmBackendContext, IrModuleFragment>? = null,
private val evaluatorFragmentInfoForPsi2Ir: EvaluatorFragmentInfo? = null,
) : CodegenFactory {
data class JvmIrBackendInput(
val irModuleFragment: IrModuleFragment,
@@ -160,7 +163,8 @@ open class JvmIrCodegenFactory(
irProviders,
pluginExtensions,
expectDescriptorToSymbol = null,
fragmentInfo = evaluatorFragmentInfoForPsi2Ir)
fragmentInfo = evaluatorFragmentInfoForPsi2Ir
)
irLinker.postProcess()
@@ -217,7 +221,8 @@ open class JvmIrCodegenFactory(
)
JvmIrSerializerImpl(state.configuration)
else null
val phaseConfig = customPhaseConfig ?: PhaseConfig(jvmPhases)
val phases = prefixPhases?.then(jvmPhases) ?: jvmPhases
val phaseConfig = customPhaseConfig ?: PhaseConfig(phases)
val context = JvmBackendContext(
state, irModuleFragment.irBuiltins, irModuleFragment, symbolTable, phaseConfig, extensions, backendExtension, irSerializer,
notifyCodegenStart,
@@ -227,7 +232,11 @@ open class JvmIrCodegenFactory(
context.state.factory.registerSourceFiles(irModuleFragment.files.map(IrFile::getKtFile))
jvmPhases.invokeToplevel(phaseConfig, context, irModuleFragment)
phases.invokeToplevel(
phaseConfig,
context,
irModuleFragment
)
// TODO: split classes into groups connected by inline calls; call this after every group
// and clear `JvmBackendContext.classCodegens`
@@ -0,0 +1,523 @@
/*
* Copyright 2010-2020 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.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.phaser.makeIrModulePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineScopeResolver
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.findInlineCallSites
import org.jetbrains.kotlin.backend.jvm.lower.SyntheticAccessorLowering.Companion.isAccessible
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.load.java.JvmAbi
// Used from CodeFragmentCompiler for IDE Debugger Plug-In
@Suppress("unused")
val reflectiveAccessLowering = makeIrModulePhase(
::ReflectiveAccessLowering,
name = "ReflectiveCalls",
description = "Avoid the need for accessors by replacing direct access to inaccessible members with accesses via reflection",
prerequisite = setOf()
)
// This lowering replaces member accesses that are illegal according to JVM
// accessibility rules with corresponding calls to the java.lang.reflect
// API. The primary use-case is to facilitate the design of the "Evaluate
// expression..." mechanism in the JVM Debugger. Here, a code fragment is
// compiled _as if_ in the context of a breakpoint. Hence, it is compiled
// against an existing class hierarchy and any access to private or otherwise
// inaccessible members that are "perceived" to be in scope must be
// transformed. The ordinary IR pipeline would introduce an accessor next to the
// access_ee_, but that is assumed to not be possible here: the accessee is
// deserialized from class files that cannot be modified at this point.
//
// The lowering looks for the following member accesses and determines their
// legality through the need for an accessor, had this been an ordinary
// compilation:
//
// - {extension, static, super*} methods, {extension} property accessors,
// functions on companion objects
// - field accesses
// - constructor invocations
// - companion object access
//
// *super calls, private or not, are not allowed from outside the class
// hierarchy of the involved classes, so is emulated in fragment compilation by
// the use of `invokespecial` - see `invokeSpecialForCall` below.
internal class ReflectiveAccessLowering(
val context: JvmBackendContext
) : IrElementTransformerVoidWithContext(), FileLoweringPass {
lateinit var inlineScopeResolver: IrInlineScopeResolver
override fun lower(irFile: IrFile) {
inlineScopeResolver = irFile.findInlineCallSites(context)
irFile.transformChildrenVoid(this)
}
// Wrapper for the logic from SyntheticAccessorLowering
private fun IrSymbol.isAccessible(withSuper: Boolean = false): Boolean {
return isAccessible(context, currentScope, inlineScopeResolver, withSuper, null)
}
// Fragments are transformed in a post-order traversal: children first,
// then parent. This obscures, in particular, dispatch receivers, that go
// from `IrGetObjectValue` calls to blocks implementing the corresponding
// reflective access . We record these _before_ transformation, in order to
// later predict the compilation strategy for fields. See the uses of
// `fieldLocationAndReceiver`.
val callsOnCompanionObjects: MutableMap<IrCall,IrClassSymbol> = mutableMapOf()
private fun recordCompanionObjectAsDispatchReceiver(expression: IrCall) {
if ((expression.dispatchReceiver as? IrGetObjectValue)?.symbol?.owner?.isCompanion == true) {
callsOnCompanionObjects[expression] = (expression.dispatchReceiver!! as IrGetObjectValue).symbol
}
}
/**
* Fragment traversal
*/
override fun visitCall(expression: IrCall): IrExpression {
recordCompanionObjectAsDispatchReceiver(expression)
expression.transformChildrenVoid(this)
val withSuper = expression.superQualifierSymbol != null
val callee = expression.symbol
if (callee.isAccessible(withSuper)) {
return expression
}
return if (expression.origin == IrStatementOrigin.GET_PROPERTY) {
generateReflectiveAccessForGetter(expression)
} else if (expression.origin?.isAssignmentOperator() == true) {
generateReflectiveAccessForSetter(expression)
} else if (expression.dispatchReceiver == null && expression.extensionReceiver == null) {
generateReflectiveStaticCall(expression)
} else if (withSuper) {
generateInvokeSpecialForCall(expression)
} else {
generateReflectiveMethodInvocation(expression)
}
}
override fun visitGetField(expression: IrGetField): IrExpression {
expression.transformChildrenVoid(this)
val field = expression.symbol
return if (field.isAccessible()) {
expression
} else {
generateReflectiveFieldGet(expression)
}
}
override fun visitSetField(expression: IrSetField): IrExpression {
expression.transformChildrenVoid(this)
val field = expression.symbol
return if (field.isAccessible()) {
expression
} else if (field.owner.correspondingPropertySymbol?.owner?.isConst == true || (field.owner.isFromJava() && field.owner.isFinal)) {
generateThrowIllegalAccessException(expression)
} else {
generateReflectiveFieldSet(expression)
}
}
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
expression.transformChildrenVoid(this)
val callee = expression.symbol
return if (callee.isAccessible()) {
expression
} else {
generateReflectiveConstructorInvocation(expression)
}
}
override fun visitGetObjectValue(expression: IrGetObjectValue): IrExpression {
expression.transformChildrenVoid(this)
val callee = expression.symbol
return if (callee.isAccessible()) {
expression
} else {
generateReflectiveAccessForCompanion(expression)
}
}
/**
* IR Generation for java.lang.reflect.{field, method, constructor} API
*/
private val symbols = context.ir.symbols
private fun IrBuilderWithScope.javaClassObject(klass: IrType): IrExpression =
irCall(symbols.kClassJava.owner.getter!!).apply {
extensionReceiver =
IrClassReferenceImpl(
startOffset, endOffset,
context.irBuiltIns.kClassClass.starProjectedType,
context.irBuiltIns.kClassClass,
klass
)
}
private fun IrBuilderWithScope.getDeclaredField(declaringClass: IrExpression, fieldName: String): IrExpression =
irCall(symbols.getDeclaredField).apply {
dispatchReceiver = declaringClass
putValueArgument(0, irString(fieldName))
}
private fun IrBuilderWithScope.fieldSetAccessible(field: IrExpression): IrExpression =
irCall(symbols.javaLangReflectFieldSetAccessible).apply {
dispatchReceiver = field
putValueArgument(0, irTrue())
}
private fun IrBuilderWithScope.fieldSet(fieldObject: IrExpression, receiver: IrExpression, value: IrExpression): IrExpression =
irCall(symbols.javaLangReflectFieldSet).apply {
dispatchReceiver = fieldObject
putValueArgument(0, receiver)
putValueArgument(1, value)
}
private fun IrBuilderWithScope.fieldGet(fieldObject: IrExpression, receiver: IrExpression): IrExpression =
irCall(symbols.javaLangReflectFieldGet).apply {
dispatchReceiver = fieldObject
putValueArgument(0, receiver)
}
private fun IrBuilderWithScope.getDeclaredMethod(
declaringClass: IrExpression,
methodName: String,
parameterTypes: List<IrType>
): IrExpression =
irCall(symbols.getDeclaredMethod).apply {
dispatchReceiver = declaringClass
putValueArgument(0, irString(methodName))
putValueArgument(1, irVararg(symbols.javaLangClass.defaultType, parameterTypes.map { javaClassObject(it) }))
}
private fun IrBuilderWithScope.methodSetAccessible(method: IrExpression): IrExpression =
irCall(symbols.javaLangReflectMethodSetAccessible).apply {
dispatchReceiver = method
putValueArgument(0, irTrue())
}
private fun IrBuilderWithScope.methodInvoke(
method: IrExpression,
receiver: IrExpression,
arguments: List<IrExpression>
): IrExpression =
irCall(symbols.javaLangReflectMethodInvoke).apply {
dispatchReceiver = method
putValueArgument(0, receiver)
putValueArgument(1, irVararg(context.irBuiltIns.anyNType, arguments))
}
private fun IrBuilderWithScope.getDeclaredConstructor(
declaringClass: IrExpression,
parameterTypes: List<IrType>
): IrExpression =
irCall(symbols.getDeclaredConstructor).apply {
dispatchReceiver = declaringClass
putValueArgument(0, irVararg(symbols.javaLangClass.defaultType, parameterTypes.map { javaClassObject(it) }))
}
private fun IrBuilderWithScope.constructorSetAccessible(constructor: IrExpression): IrExpression =
irCall(symbols.javaLangReflectConstructorSetAccessible).apply {
dispatchReceiver = constructor
putValueArgument(0, irTrue())
}
private fun IrBuilderWithScope.constructorNewInstance(constructor: IrExpression, arguments: List<IrExpression>): IrExpression =
irCall(symbols.javaLangReflectConstructorNewInstance).apply {
dispatchReceiver = constructor
putValueArgument(0, irVararg(context.irBuiltIns.anyNType, arguments))
}
/**
* Specific reflective "patches"
*/
private fun generateReflectiveMethodInvocation(
declaringClass: IrType,
methodName: String,
parameterTypes: List<IrType>,
receiver: IrExpression?, // null => static method on `declaringClass`
arguments: List<IrExpression>,
returnType: IrType,
symbol: IrSymbol
): IrExpression =
context.createJvmIrBuilder(symbol).irBlock(resultType = returnType) {
val methodVar =
createTmpVariable(
getDeclaredMethod(
javaClassObject(declaringClass),
methodName,
parameterTypes
),
nameHint = "method",
irType = symbols.javaLangReflectMethod.defaultType
)
+methodSetAccessible(irGet(methodVar))
+methodInvoke(irGet(methodVar), receiver ?: irNull(), arguments)
}
private fun IrFunctionAccessExpression.getValueArguments(): List<IrExpression> =
(0 until valueArgumentsCount).map { getValueArgument(it)!! }
private fun IrFunctionAccessExpression.valueParameterTypes(): List<IrType> =
symbol.owner.valueParameters.map { it.type }
private fun generateReflectiveMethodInvocation(call: IrCall): IrExpression =
generateReflectiveMethodInvocation(
call.superQualifierSymbol?.defaultType ?: call.dispatchReceiver!!.type,
call.symbol.owner.name.asString(),
mutableListOf<IrType>().apply {
call.symbol.owner.extensionReceiverParameter?.let { add(it.type) }
addAll(call.valueParameterTypes())
},
call.dispatchReceiver!!,
mutableListOf<IrExpression>().apply {
call.extensionReceiver?.let { add(it) }
addAll(call.getValueArguments())
},
call.type,
call.symbol
)
private fun generateReflectiveStaticCall(call: IrCall): IrExpression {
assert(call.dispatchReceiver == null) { "Assumed-to-be static call with a dispatch receiver" }
return generateReflectiveMethodInvocation(
call.symbol.owner.parentAsClass.defaultType,
call.symbol.owner.name.asString(),
call.valueParameterTypes(),
null, // static call
call.getValueArguments(),
call.type,
call.symbol
)
}
private fun generateReflectiveConstructorInvocation(call: IrConstructorCall): IrExpression =
context.createJvmIrBuilder(call.symbol)
.irBlock(resultType = call.type) {
val constructorVar =
createTmpVariable(
getDeclaredConstructor(
javaClassObject(call.symbol.owner.parentAsClass.defaultType),
call.valueParameterTypes()
),
nameHint = "constructor",
irType = symbols.javaLangReflectConstructor.defaultType
)
+constructorSetAccessible(irGet(constructorVar))
+constructorNewInstance(irGet(constructorVar), call.getValueArguments())
}
private fun generateReflectiveFieldGet(
declaringClass: IrType,
fieldName: String,
fieldType: IrType,
instance: IrExpression?, // null ==> static field on `declaringClass`
symbol: IrSymbol,
): IrExpression =
context.createJvmIrBuilder(symbol)
.irBlock(resultType = fieldType) {
val classVar = createTmpVariable(
javaClassObject(declaringClass),
nameHint = "klass",
irType = symbols.kClassJava.owner.getter!!.returnType
)
val fieldVar = createTmpVariable(
getDeclaredField(irGet(classVar), fieldName),
nameHint = "field",
irType = symbols.javaLangReflectField.defaultType
)
+fieldSetAccessible(irGet(fieldVar))
+fieldGet(irGet(fieldVar), instance ?: irGet(classVar))
}
private fun generateReflectiveFieldGet(getField: IrGetField): IrExpression =
generateReflectiveFieldGet(
getField.symbol.owner.parentClassOrNull!!.defaultType,
getField.symbol.owner.name.asString(),
getField.type,
getField.receiver,
getField.symbol
)
private fun generateReflectiveFieldSet(
declaringClass: IrType,
fieldName: String,
value: IrExpression,
type: IrType,
instance: IrExpression?,
symbol: IrSymbol
): IrExpression {
return context.createJvmIrBuilder(symbol)
.irBlock(resultType = type) {
val fieldVar =
createTmpVariable(
getDeclaredField(
javaClassObject(declaringClass),
fieldName
),
nameHint = "field",
irType = symbols.javaLangReflectField.defaultType
)
+fieldSetAccessible(irGet(fieldVar))
+fieldSet(irGet(fieldVar), instance ?: irNull(), value)
}
}
private fun generateReflectiveFieldSet(setField: IrSetField): IrExpression =
generateReflectiveFieldSet(
setField.symbol.owner.parentClassOrNull!!.defaultType,
setField.symbol.owner.name.asString(),
setField.value,
setField.type,
setField.receiver,
setField.symbol,
)
private fun shouldUseAccessor(accessor: IrSimpleFunction): Boolean {
return (context.generatorExtensions as StubGeneratorExtensions).isAccessorWithExplicitImplementation(accessor)
}
// Returns a pair of the _type_ containing the field and the _instance_ on
// which the field should be accessed. The instance is `null` if the field
// is static. If the field is on a companion object it will be generated on
// the corresponding owning class (recall, at this point the field has been
// absolutely determined to be inaccessible to outside code).
private fun fieldLocationAndReceiver(call: IrCall): Pair<IrType, IrExpression?> {
callsOnCompanionObjects[call]?.let {
val parentAsClass = it.owner.parentAsClass
if (!parentAsClass.isJvmInterface) {
return parentAsClass.defaultType to null
}
}
return call.dispatchReceiver!!.type to call.dispatchReceiver!!
}
private fun generateReflectiveAccessForGetter(call: IrCall): IrExpression {
val getter = call.symbol.owner
val property = getter.correspondingPropertySymbol!!.owner
if (shouldUseAccessor(getter)) {
return generateReflectiveMethodInvocation(
getter.parentAsClass.defaultType,
JvmAbi.getterName(propertyName = property.name.asString()),
getter.extensionReceiverParameter?.let { listOf(it.type) } ?: listOf(),
call.dispatchReceiver!!,
listOfNotNull(call.extensionReceiver),
getter.returnType,
call.symbol
)
}
val (fieldLocation, instance) = fieldLocationAndReceiver(call)
return generateReflectiveFieldGet(
fieldLocation,
property.name.asString(),
getter.returnType,
instance,
call.symbol,
)
}
private fun generateReflectiveAccessForSetter(call: IrCall): IrExpression {
val setter = call.symbol.owner
val property = setter.correspondingPropertySymbol!!.owner
if (shouldUseAccessor(setter)) {
return generateReflectiveMethodInvocation(
setter.parentAsClass.defaultType,
JvmAbi.setterName(propertyName = property.name.asString()),
mutableListOf<IrType>().apply {
setter.extensionReceiverParameter?.let { add(it.type) }
addAll(call.valueParameterTypes())
},
call.dispatchReceiver!!,
mutableListOf<IrExpression>().apply {
call.extensionReceiver?.let { add(it) }
addAll(call.getValueArguments())
},
setter.returnType,
call.symbol
)
}
val (fieldLocation, receiver) = fieldLocationAndReceiver(call)
return generateReflectiveFieldSet(
fieldLocation,
call.symbol.owner.correspondingPropertySymbol!!.owner.name.asString(),
call.getValueArgument(0)!!,
call.type,
receiver,
call.symbol
)
}
private fun generateThrowIllegalAccessException(setField: IrSetField): IrExpression {
return context.createJvmIrBuilder(setField.symbol).irBlock {
+irCall(symbols.throwIllegalAccessException).apply {
putValueArgument(0, irString("Can not set final field"))
}
}
}
// This is needed to coerce the codegen to emit a very specific
// invokespecial instruction to target a super-call that is otherwise
// illegal on the JVM. However! The byte code from this compilation is
// not run on a JVM: it is interpreted by eval4j. Eval4j handles
// invokespecial via JDI from which it *is* possible to do the required
// super call.
private fun generateInvokeSpecialForCall(expression: IrCall): IrExpression {
val jvmSignature = context.methodSignatureMapper.mapSignatureSkipGeneric(expression.symbol.owner)
val owner = expression.superQualifierSymbol!!.owner
val builder = context.createJvmIrBuilder(expression.symbol)
// invokeSpecial(owner: String, name: String, descriptor: String, isInterface: Boolean): T
return builder.irCall(context.irIntrinsics.symbols.jvmDebuggerInvokeSpecialIntrinsic).apply {
dispatchReceiver = expression.dispatchReceiver
this.type = expression.symbol.owner.returnType
putValueArgument(0, builder.irString("${owner.packageFqName}/${owner.name}"))
putValueArgument(1, builder.irString(jvmSignature.asmMethod.name))
putValueArgument(2, builder.irString(jvmSignature.asmMethod.descriptor))
putValueArgument(3, builder.irFalse())
}
}
private fun generateReflectiveAccessForCompanion(call: IrGetObjectValue): IrExpression =
generateReflectiveFieldGet(
call.symbol.owner.parentAsClass.defaultType,
"Companion",
call.type,
null,
call.symbol
)
}
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.FileLoweringPass
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.backend.common.ScopeWithIr
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedString
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
@@ -51,6 +52,59 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileL
(accessor.parent as IrDeclarationContainer).declarations.add(accessor)
}
}
companion object {
fun IrSymbol.isAccessible(
context: JvmBackendContext,
currentScope: ScopeWithIr?,
inlineScopeResolver: IrInlineScopeResolver,
withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean {
/// We assume that IR code that reaches us has been checked for correctness at the frontend.
/// This function needs to single out those cases where Java accessibility rules differ from Kotlin's.
val declarationRaw = owner as IrDeclarationWithVisibility
// If this expression won't actually result in a JVM instruction call, access modifiers don't matter.
if (declarationRaw is IrFunction && (declarationRaw.isInline || context.irIntrinsics.getIntrinsic(declarationRaw.symbol) != null))
return true
// Enum entry constructors are generated as package-private and are accessed only from corresponding enum class
if (declarationRaw is IrConstructor && declarationRaw.constructedClass.isEnumEntry) return true
// Public declarations are already accessible. However, `super` calls are subclass-only.
val jvmVisibility = AsmUtil.getVisibilityAccessFlag(declarationRaw.visibility.delegate)
if (jvmVisibility == Opcodes.ACC_PUBLIC && !withSuper) return true
// `toArray` is always accessible cause mapped to public functions
if (declarationRaw is IrSimpleFunction && (declarationRaw.isNonGenericToArray() || declarationRaw.isGenericToArray(context)) &&
declarationRaw.parentAsClass.isCollectionSubClass
) return true
// `$assertionsDisabled` is accessed only from the same class, even in an inline function
// (the inliner will generate it at the call site if necessary).
if (declarationRaw is IrField && declarationRaw.isAssertionsDisabledField(context)) return true
val declaration = when (declarationRaw) {
is IrSimpleFunction -> declarationRaw.resolveFakeOverride(allowAbstract = true)!!
is IrField -> declarationRaw.resolveFakeOverride()
else -> declarationRaw
}
val ownerClass = declaration.parent as? IrClass ?: return true // locals are always accessible
val scopeClassOrPackage = inlineScopeResolver.findContainer(currentScope!!.irElement) ?: return false
val samePackage = ownerClass.getPackageFragment()?.fqName == scopeClassOrPackage.getPackageFragment()?.fqName
return when {
jvmVisibility == 0 /* package only */ -> samePackage
jvmVisibility == Opcodes.ACC_PRIVATE -> ownerClass == scopeClassOrPackage
// JVM `protected`, unlike Kotlin `protected`, permits accesses from the same package.
!withSuper && samePackage -> true
// Super calls and cross-package protected accesses are both only possible from a subclass of the declaration
// owner. Also, the target of a non-static call must be assignable to the current class. This is a verification
// constraint: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.1.8
else -> (scopeClassOrPackage is IrClass && scopeClassOrPackage.isSubclassOf(ownerClass)) &&
(thisObjReference == null || thisObjReference.owner.isSubclassOf(scopeClassOrPackage))
}
}
}
}
private class SyntheticAccessorTransformer(
@@ -71,6 +125,11 @@ private class SyntheticAccessorTransformer(
private val getterMap = mutableMapOf<FieldKey, IrSimpleFunctionSymbol>()
private val setterMap = mutableMapOf<FieldKey, IrSimpleFunctionSymbol>()
private fun IrSymbol.isAccessible(withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean =
with(SyntheticAccessorLowering) {
isAccessible(context, currentScope, inlineScopeResolver, withSuper, thisObjReference)
}
override fun visitFunctionAccess(expression: IrFunctionAccessExpression): IrExpression {
if (expression.usesDefaultArguments()) {
return super.visitFunctionAccess(expression)
@@ -702,53 +761,6 @@ private class SyntheticAccessorTransformer(
// we generate a suffix to distinguish access to field with different receiver types in the super hierarchy.
return "p" + if (isStatic && visibility.isProtected) "\$s" + parentAsClass.syntheticAccessorToSuperSuffix() else ""
}
private fun IrSymbol.isAccessible(withSuper: Boolean, thisObjReference: IrClassSymbol?): Boolean {
/// We assume that IR code that reaches us has been checked for correctness at the frontend.
/// This function needs to single out those cases where Java accessibility rules differ from Kotlin's.
val declarationRaw = owner as IrDeclarationWithVisibility
// If this expression won't actually result in a JVM instruction call, access modifiers don't matter.
if (declarationRaw is IrFunction && (declarationRaw.isInline || context.irIntrinsics.getIntrinsic(declarationRaw.symbol) != null))
return true
// Enum entry constructors are generated as package-private and are accessed only from corresponding enum class
if (declarationRaw is IrConstructor && declarationRaw.constructedClass.isEnumEntry) return true
// Public declarations are already accessible. However, `super` calls are subclass-only.
val jvmVisibility = AsmUtil.getVisibilityAccessFlag(declarationRaw.visibility.delegate)
if (jvmVisibility == Opcodes.ACC_PUBLIC && !withSuper) return true
// `toArray` is always accessible cause mapped to public functions
if (declarationRaw is IrSimpleFunction && (declarationRaw.isNonGenericToArray() || declarationRaw.isGenericToArray(context)) &&
declarationRaw.parentAsClass.isCollectionSubClass
) return true
// `$assertionsDisabled` is accessed only from the same class, even in an inline function
// (the inliner will generate it at the call site if necessary).
if (declarationRaw is IrField && declarationRaw.isAssertionsDisabledField(context)) return true
val declaration = when (declarationRaw) {
is IrSimpleFunction -> declarationRaw.resolveFakeOverride(allowAbstract = true)!!
is IrField -> declarationRaw.resolveFakeOverride()
else -> declarationRaw
}
val ownerClass = declaration.parent as? IrClass ?: return true // locals are always accessible
val scopeClassOrPackage = inlineScopeResolver.findContainer(currentScope!!.irElement) ?: return false
val samePackage = ownerClass.getPackageFragment()?.fqName == scopeClassOrPackage.getPackageFragment()?.fqName
return when {
jvmVisibility == 0 /* package only */ -> samePackage
jvmVisibility == Opcodes.ACC_PRIVATE -> ownerClass == scopeClassOrPackage
// JVM `protected`, unlike Kotlin `protected`, permits accesses from the same package.
!withSuper && samePackage -> true
// Super calls and cross-package protected accesses are both only possible from a subclass of the declaration
// owner. Also, the target of a non-static call must be assignable to the current class. This is a verification
// constraint: https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10.1.8
else -> (scopeClassOrPackage is IrClass && scopeClassOrPackage.isSubclassOf(ownerClass)) &&
(thisObjReference == null || thisObjReference.owner.isSubclassOf(scopeClassOrPackage))
}
}
}
private fun IrClass.syntheticAccessorToSuperSuffix(): String =
@@ -5,10 +5,8 @@
package org.jetbrains.kotlin.backend.jvm
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
interface JvmGeneratorExtensions {
@@ -17,7 +17,7 @@ import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_CALL_RESULT_NAME
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_CREATE_METHOD_NAME
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.InlineClassRepresentation
import org.jetbrains.kotlin.descriptors.Modality
@@ -60,6 +60,7 @@ class JvmSymbols(
private val kotlinReflectPackage: IrPackageFragment = createPackage(FqName("kotlin.reflect"))
private val javaLangPackage: IrPackageFragment = createPackage(FqName("java.lang"))
private val javaLangInvokePackage: IrPackageFragment = createPackage(FqName("java.lang.invoke"))
private val javaLangReflectPackage: IrPackageFragment = createPackage(FqName("java.lang.reflect"))
private val javaUtilPackage: IrPackageFragment = createPackage(FqName("java.util"))
@@ -104,6 +105,7 @@ class JvmSymbols(
"kotlin.reflect" -> kotlinReflectPackage
"java.lang" -> javaLangPackage
"java.lang.invoke" -> javaLangInvokePackage
"java.lang.reflect" -> javaLangReflectPackage
"java.util" -> javaUtilPackage
"kotlin.internal" -> kotlinInternalPackage
else -> error("Other packages are not supported yet: $fqName")
@@ -121,6 +123,9 @@ class JvmSymbols(
klass.addFunction("throwTypeCastException", irBuiltIns.nothingType, isStatic = true).apply {
addValueParameter("message", irBuiltIns.stringType)
}
klass.addFunction("throwIllegalAccessException", irBuiltIns.nothingType, isStatic = true).apply {
addValueParameter("message", irBuiltIns.stringType)
}
klass.addFunction("throwUnsupportedOperationException", irBuiltIns.nothingType, isStatic = true).apply {
addValueParameter("message", irBuiltIns.stringType)
}
@@ -171,6 +176,9 @@ class JvmSymbols(
override val throwTypeCastException: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "throwTypeCastException" }
val throwIllegalAccessException: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "throwIllegalAccessException" }
val throwUnsupportedOperationException: IrSimpleFunctionSymbol =
intrinsicsClass.functions.single { it.owner.name.asString() == "throwUnsupportedOperationException" }
@@ -224,12 +232,103 @@ class JvmSymbols(
private val kDeclarationContainer: IrClassSymbol =
createClass(StandardNames.FqNames.kDeclarationContainer.toSafe(), ClassKind.INTERFACE, Modality.ABSTRACT)
val javaLangReflectField: IrClassSymbol =
createClass(FqName("java.lang.reflect.Field")) { klass ->
klass.addFunction("setAccessible", irBuiltIns.unitType).apply {
addValueParameter("isAccessible", irBuiltIns.booleanType)
}
klass.addFunction("get", irBuiltIns.anyNType).apply {
addValueParameter("receiver", irBuiltIns.anyNType)
}
klass.addFunction("set", irBuiltIns.unitType).apply {
addValueParameter("receiver", irBuiltIns.anyNType)
addValueParameter("value", irBuiltIns.anyNType)
}
}
val javaLangReflectMethod: IrClassSymbol =
createClass(FqName("java.lang.reflect.Method")) { klass ->
klass.addFunction("setAccessible", irBuiltIns.unitType).apply {
addValueParameter("isAccessible", irBuiltIns.booleanType)
}
klass.addFunction("invoke", irBuiltIns.anyNType).apply {
addValueParameter("receiver", irBuiltIns.anyNType)
addValueParameter {
name = Name.identifier("args")
type = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyNType)
varargElementType = irBuiltIns.anyNType
}
}
}
val javaLangReflectConstructor: IrClassSymbol =
createClass(FqName("java.lang.reflect.Constructor")) { klass ->
klass.addFunction("setAccessible", irBuiltIns.unitType).apply {
addValueParameter("isAccessible", irBuiltIns.booleanType)
}
klass.addFunction("newInstance", irBuiltIns.anyNType).apply {
addValueParameter {
name = Name.identifier("args")
type = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyNType)
varargElementType = irBuiltIns.anyNType
}
}
}
val javaLangReflectFieldSetAccessible: IrSimpleFunctionSymbol =
javaLangReflectField.functionByName("setAccessible")
val javaLangReflectMethodSetAccessible: IrSimpleFunctionSymbol =
javaLangReflectMethod.functionByName("setAccessible")
val javaLangReflectConstructorSetAccessible: IrSimpleFunctionSymbol =
javaLangReflectConstructor.functionByName("setAccessible")
val javaLangClass: IrClassSymbol =
createClass(FqName("java.lang.Class")) { klass ->
klass.addTypeParameter("T", irBuiltIns.anyNType, Variance.INVARIANT)
klass.addFunction("desiredAssertionStatus", irBuiltIns.booleanType)
klass.addFunction("getDeclaredMethod", javaLangReflectMethod.defaultType.makeNullable()).apply {
addValueParameter("methodName", irBuiltIns.stringType.makeNullable())
addValueParameter {
name = Name.identifier("args")
type = irBuiltIns.arrayClass.typeWith(klass.defaultType).makeNullable()
varargElementType = klass.defaultType
}
}
klass.addFunction("getDeclaredField", javaLangReflectField.defaultType).apply {
addValueParameter("fieldName", irBuiltIns.stringType)
}
klass.addFunction("getDeclaredConstructor", javaLangReflectConstructor.defaultType.makeNullable()).apply {
addValueParameter {
name = Name.identifier("args")
type = irBuiltIns.arrayClass.typeWith(klass.defaultType).makeNullable()
varargElementType = klass.defaultType
}
}
}
val getDeclaredField: IrSimpleFunctionSymbol =
javaLangClass.functionByName("getDeclaredField")
val getDeclaredMethod: IrSimpleFunctionSymbol =
javaLangClass.functionByName("getDeclaredMethod")
val getDeclaredConstructor: IrSimpleFunctionSymbol =
javaLangClass.functionByName("getDeclaredConstructor")
val javaLangReflectFieldGet: IrSimpleFunctionSymbol =
javaLangReflectField.functionByName("get")
val javaLangReflectFieldSet: IrSimpleFunctionSymbol =
javaLangReflectField.functionByName("set")
val javaLangReflectMethodInvoke: IrSimpleFunctionSymbol =
javaLangReflectMethod.functionByName("invoke")
val javaLangReflectConstructorNewInstance: IrSimpleFunctionSymbol =
javaLangReflectConstructor.functionByName("newInstance")
private val javaLangDeprecatedWithDeprecatedFlag: IrClassSymbol =
createClass(FqName("java.lang.Deprecated"), classKind = ClassKind.ANNOTATION_CLASS) { klass ->
klass.addConstructor { isPrimary = true }
@@ -735,6 +834,19 @@ class JvmSymbols(
returnType = irBuiltIns.anyType
}.symbol
val jvmDebuggerInvokeSpecialIntrinsic: IrSimpleFunctionSymbol =
irFactory.buildFun {
name = Name.special("<jvm-debugger-invokespecial>")
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
}.apply {
parent = kotlinJvmInternalPackage
addValueParameter("owner", irBuiltIns.stringType)
addValueParameter("name", irBuiltIns.stringType)
addValueParameter("descriptor", irBuiltIns.stringType)
addValueParameter("isInterface", irBuiltIns.booleanType)
returnType = irBuiltIns.anyNType
}.symbol
private val collectionToArrayClass: IrClassSymbol = createClass(FqName("kotlin.jvm.internal.CollectionToArray")) { klass ->
klass.origin = JvmLoweredDeclarationOrigin.TO_ARRAY
@@ -86,8 +86,10 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
symbols.throwNullPointerException.toKey()!! to ThrowException(Type.getObjectType("java/lang/NullPointerException")),
symbols.throwTypeCastException.toKey()!! to ThrowException(Type.getObjectType("kotlin/TypeCastException")),
symbols.throwUnsupportedOperationException.toKey()!! to ThrowException(Type.getObjectType("java/lang/UnsupportedOperationException")),
symbols.throwIllegalAccessException.toKey()!! to ThrowException(Type.getObjectType("java/lang/IllegalAccessException")),
symbols.throwKotlinNothingValueException.toKey()!! to ThrowKotlinNothingValueException,
symbols.jvmIndyIntrinsic.toKey()!! to JvmInvokeDynamic,
symbols.jvmDebuggerInvokeSpecialIntrinsic.toKey()!! to JvmDebuggerInvokeSpecial,
symbols.intPostfixIncr.toKey()!! to PostfixIinc(1),
symbols.intPostfixDecr.toKey()!! to PostfixIinc(-1)
) +
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2021 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.backend.jvm.intrinsics
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.org.objectweb.asm.Type
// This intrinsic enables IR lowerings to force the generation of a particular
// invokeSpecial instruction in the resulting JVM bytecode.
//
// The need for this is to coerce the IR codegen backend to generate an
// otherwise illegal invokeSpecial for the express purpose of being
// _interpreted_ by eval4j in the fragment evaluator and not actually run on
// the JVM. This allows the "evaluate expression" functionality of the Kotlin
// JVM Debugger Plug-in in IntelliJ to simulate the invocation of `super` calls
// in the context of a breakpoint.
//
// It uses the "trick" of encoding the desired operands as constants passed as
// arguments to the intrinsic, opening a direct line from the producing
// lowering straight through to JVM codegen without interference from
// lowerings in between.
object JvmDebuggerInvokeSpecial : IntrinsicMethod() {
override fun invoke(expression: IrFunctionAccessExpression, codegen: ExpressionCodegen, data: BlockInfo): PromisedValue {
fun fail(message: String): Nothing =
throw AssertionError("$message; expression:\n${expression.dump()}")
val owner = expression.getValueArgument(0)?.getStringConst()
?: fail("'owner' is expected to be a string const")
val name = expression.getValueArgument(1)?.getStringConst()
?: fail("'name' is expected to be a string const")
val descriptor = expression.getValueArgument(2)?.getStringConst()
?: fail("'descriptor' is expected to be a string const")
val isInterface = expression.getValueArgument(3)?.getBooleanConst()
?: fail("'isInterface' is expected to be a boolean const")
expression.dispatchReceiver!!.accept(codegen, data).materialize()
codegen.mv.invokespecial(owner, name, descriptor, isInterface)
return MaterialValue(codegen, Type.getReturnType(descriptor), expression.type)
}
}
@@ -202,17 +202,18 @@ class CallGenerator(statementGenerator: StatementGenerator) : StatementGenerator
val irType = descriptor.type.toIrType()
return if (getMethodDescriptor == null) {
call.callReceiver.call { dispatchReceiverValue, _ ->
val superQualifierSymbol = (call.superQualifier ?: descriptor.containingDeclaration as? ClassDescriptor)?.let {
if (it is ScriptDescriptor) null // otherwise it creates a reference to script as class; TODO: check if correct
else context.symbolTable.referenceClass(it)
}
val fieldSymbol = context.symbolTable.referenceField(descriptor.resolveFakeOverride().original)
val superQualifierSymbol = (call.superQualifier ?: descriptor.containingDeclaration as? ClassDescriptor)?.let {
if (it is ScriptDescriptor) null // otherwise it creates a reference to script as class; TODO: check if correct
else context.symbolTable.referenceClass(it)
}
val fieldSymbol =
context.symbolTable.referenceField(context.extensions.remapDebuggerFieldPropertyDescriptor(descriptor.resolveFakeOverride().original))
call.callReceiver.call { dispatchReceiverValue, extensionReceiverValue ->
IrGetFieldImpl(
startOffset, endOffset,
fieldSymbol,
irType,
dispatchReceiverValue?.load(),
dispatchReceiverValue?.load() ?: extensionReceiverValue?.load(),
IrStatementOrigin.GET_PROPERTY,
superQualifierSymbol
).also { context.callToSubstitutedDescriptorMap[it] = descriptor }
@@ -42,4 +42,6 @@ open class GeneratorExtensions : StubGeneratorExtensions() {
open fun getPreviousScripts(): List<IrScriptSymbol>? = null
open fun unwrapSyntheticJavaProperty(descriptor: PropertyDescriptor): Pair<FunctionDescriptor, FunctionDescriptor?>? = null
open fun remapDebuggerFieldPropertyDescriptor(propertyDescriptor: PropertyDescriptor): PropertyDescriptor = propertyDescriptor
}
@@ -15,9 +15,7 @@ import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.util.IdSignatureComposer
import org.jetbrains.kotlin.ir.util.NameProvider
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.psi2ir.generators.fragments.EvaluatorFragmentInfo
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ThisClassReceiver
// Used from CodeFragmentCompiler for IDE Debugger Plug-In
@@ -121,3 +121,14 @@ fun IrStatementOrigin.isAssignmentOperatorWithResult() =
else ->
false
}
fun IrStatementOrigin.isAssignmentOperator(): Boolean =
when (this) {
IrStatementOrigin.EQ,
IrStatementOrigin.PLUSEQ,
IrStatementOrigin.MINUSEQ,
IrStatementOrigin.MULTEQ,
IrStatementOrigin.DIVEQ,
IrStatementOrigin.PERCEQ -> true
else -> isAssignmentOperatorWithResult()
}
@@ -9,7 +9,6 @@ import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.lazy.IrLazyClass
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedContainerSource
import org.jetbrains.kotlin.types.KotlinType
@@ -35,6 +34,15 @@ open class StubGeneratorExtensions {
// intercept and supply "fake" deserialized sources.
open fun getContainerSource(descriptor: DeclarationDescriptor): DeserializedContainerSource? = null
// Extension point for the JVM Debugger IDEA plug-in: to replace accesses
// to private properties _without_ accessor implementations, the fragment
// compiler needs to predict the compilation output for properties.
// To do this, we need to know whether the property accessors have explicit
// bodies, information that is _not_ present in the IR structure, but _is_
// available in the corresponding PSI. See `CodeFragmentCompiler` in the
// plug-in for the implementation.
open fun isAccessorWithExplicitImplementation(accessor: IrSimpleFunction): Boolean = false
open fun isPropertyWithPlatformField(descriptor: PropertyDescriptor): Boolean = false
open fun isStaticFunction(descriptor: FunctionDescriptor): Boolean = false