diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java.201 b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java.201 new file mode 100644 index 00000000000..9339caf91e8 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/AbstractClassBuilder.java.201 @@ -0,0 +1,158 @@ +/* + * Copyright 2010-2015 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 com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.codegen.inline.FileMapping; +import org.jetbrains.kotlin.codegen.inline.SMAPBuilder; +import org.jetbrains.kotlin.codegen.inline.SourceMapper; +import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; +import org.jetbrains.org.objectweb.asm.*; + +import java.util.List; + +import static org.jetbrains.kotlin.codegen.inline.InlineCodegenUtilsKt.GENERATE_SMAP; + +public abstract class AbstractClassBuilder implements ClassBuilder { + protected static final MethodVisitor EMPTY_METHOD_VISITOR = new MethodVisitor(Opcodes.API_VERSION) {}; + protected static final FieldVisitor EMPTY_FIELD_VISITOR = new FieldVisitor(Opcodes.API_VERSION) {}; + + private String thisName; + + private final JvmSerializationBindings serializationBindings = new JvmSerializationBindings(); + + private String sourceName; + + private String debugInfo; + + public static class Concrete extends AbstractClassBuilder { + private final ClassVisitor v; + + public Concrete(@NotNull ClassVisitor v) { + this.v = v; + } + + @Override + @NotNull + public ClassVisitor getVisitor() { + return v; + } + } + + @Override + @NotNull + public FieldVisitor newField( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable Object value + ) { + FieldVisitor visitor = getVisitor().visitField(access, name, desc, signature, value); + if (visitor == null) { + return EMPTY_FIELD_VISITOR; + } + return visitor; + } + + @Override + @NotNull + public MethodVisitor newMethod( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable String[] exceptions + ) { + MethodVisitor visitor = getVisitor().visitMethod(access, name, desc, signature, exceptions); + if (visitor == null) { + return EMPTY_METHOD_VISITOR; + } + return visitor; + } + + @Override + @NotNull + public JvmSerializationBindings getSerializationBindings() { + return serializationBindings; + } + + @Override + @NotNull + public AnnotationVisitor newAnnotation(@NotNull String desc, boolean visible) { + return getVisitor().visitAnnotation(desc, visible); + } + + @Override + public void done() { + getVisitor().visitSource(sourceName, debugInfo); + getVisitor().visitEnd(); + } + + @Override + public void defineClass( + @Nullable PsiElement origin, + int version, + int access, + @NotNull String name, + @Nullable String signature, + @NotNull String superName, + @NotNull String[] interfaces + ) { + thisName = name; + getVisitor().visit(version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(@NotNull String name, @Nullable String debug) { + assert sourceName == null || sourceName.equals(name) : "inconsistent file name: " + sourceName + " vs " + name; + sourceName = name; + debugInfo = debug; + } + + @Override + public void visitSMAP(@NotNull SourceMapper smap, boolean backwardsCompatibleSyntax) { + if (!GENERATE_SMAP) return; + + List fileMappings = smap.getResultMappings(); + if (fileMappings.isEmpty()) return; + + visitSource(fileMappings.get(0).getName(), SMAPBuilder.INSTANCE.build(fileMappings, backwardsCompatibleSyntax)); + } + + @Override + public void visitOuterClass(@NotNull String owner, @Nullable String name, @Nullable String desc) { + getVisitor().visitOuterClass(owner, name, desc); + } + + @Override + public void visitInnerClass(@NotNull String name, @Nullable String outerName, @Nullable String innerName, int access) { + getVisitor().visitInnerClass(name, outerName, innerName, access); + } + + @Override + @NotNull + public String getThisName() { + assert thisName != null : "This name isn't set"; + return thisName; + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassBuilder.java.201 b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassBuilder.java.201 new file mode 100644 index 00000000000..6e5bf2b6259 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ClassBuilder.java.201 @@ -0,0 +1,79 @@ +/* + * Copyright 2010-2015 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 com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.codegen.inline.SourceMapper; +import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; +import org.jetbrains.org.objectweb.asm.*; + +public interface ClassBuilder { + @NotNull + FieldVisitor newField( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable Object value + ); + + @NotNull + MethodVisitor newMethod( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable String[] exceptions + ); + + @NotNull + JvmSerializationBindings getSerializationBindings(); + + @NotNull + AnnotationVisitor newAnnotation(@NotNull String desc, boolean visible); + + void done(); + + @NotNull + ClassVisitor getVisitor(); + + void defineClass( + @Nullable PsiElement origin, + int version, + int access, + @NotNull String name, + @Nullable String signature, + @NotNull String superName, + @NotNull String[] interfaces + ); + + void visitSource(@NotNull String name, @Nullable String debug); + + void visitSMAP(@NotNull SourceMapper smap, boolean backwardsCompatibleSyntax); + + void visitOuterClass(@NotNull String owner, @Nullable String name, @Nullable String desc); + + void visitInnerClass(@NotNull String name, @Nullable String outerName, @Nullable String innerName, int access); + + @NotNull + String getThisName(); +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatingClassBuilder.java.201 b/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatingClassBuilder.java.201 new file mode 100644 index 00000000000..998b33ff552 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/DelegatingClassBuilder.java.201 @@ -0,0 +1,118 @@ +/* + * Copyright 2010-2015 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 com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.codegen.inline.SourceMapper; +import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; +import org.jetbrains.org.objectweb.asm.*; + +public abstract class DelegatingClassBuilder implements ClassBuilder { + @NotNull + protected abstract ClassBuilder getDelegate(); + + @NotNull + @Override + public FieldVisitor newField( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable Object value + ) { + return getDelegate().newField(origin, access, name, desc, signature, value); + } + + @NotNull + @Override + public MethodVisitor newMethod( + @NotNull JvmDeclarationOrigin origin, + int access, + @NotNull String name, + @NotNull String desc, + @Nullable String signature, + @Nullable String[] exceptions + ) { + return getDelegate().newMethod(origin, access, name, desc, signature, exceptions); + } + + @NotNull + @Override + public JvmSerializationBindings getSerializationBindings() { + return getDelegate().getSerializationBindings(); + } + + @NotNull + @Override + public AnnotationVisitor newAnnotation(@NotNull String desc, boolean visible) { + return getDelegate().newAnnotation(desc, visible); + } + + @Override + public void done() { + getDelegate().done(); + } + + @NotNull + @Override + public ClassVisitor getVisitor() { + return getDelegate().getVisitor(); + } + + @Override + public void defineClass( + @Nullable PsiElement origin, + int version, + int access, + @NotNull String name, + @Nullable String signature, + @NotNull String superName, + @NotNull String[] interfaces + ) { + getDelegate().defineClass(origin, version, access, name, signature, superName, interfaces); + } + + @Override + public void visitSource(@NotNull String name, @Nullable String debug) { + getDelegate().visitSource(name, debug); + } + + @Override + public void visitSMAP(@NotNull SourceMapper smap, boolean backwardsCompatibleSyntax) { + getDelegate().visitSMAP(smap, backwardsCompatibleSyntax); + } + + @Override + public void visitOuterClass(@NotNull String owner, @Nullable String name, @Nullable String desc) { + getDelegate().visitOuterClass(owner, name, desc); + } + + @Override + public void visitInnerClass(@NotNull String name, @Nullable String outerName, @Nullable String innerName, int access) { + getDelegate().visitInnerClass(name, outerName, innerName, access); + } + + @NotNull + @Override + public String getThisName() { + return getDelegate().getThisName(); + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java.201 b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java.201 new file mode 100644 index 00000000000..f2c8aa9d96d --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ImplementationBodyCodegen.java.201 @@ -0,0 +1,1248 @@ +/* + * 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.codegen; + +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.psi.PsiElement; +import com.intellij.util.ArrayUtil; +import kotlin.Unit; +import kotlin.collections.CollectionsKt; +import kotlin.jvm.functions.Function2; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.backend.common.DataClassMethodGenerator; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; +import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap; +import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap.PlatformMutabilityMapping; +import org.jetbrains.kotlin.codegen.binding.CodegenBinding; +import org.jetbrains.kotlin.codegen.binding.MutableClosure; +import org.jetbrains.kotlin.codegen.context.*; +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension; +import org.jetbrains.kotlin.codegen.serialization.JvmSerializerExtension; +import org.jetbrains.kotlin.codegen.signature.BothSignatureWriter; +import org.jetbrains.kotlin.codegen.signature.JvmSignatureWriter; +import org.jetbrains.kotlin.codegen.state.GenerationState; +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.config.LanguageFeature; +import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.incremental.components.NoLookupLocation; +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor; +import org.jetbrains.kotlin.load.kotlin.TypeMappingMode; +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader; +import org.jetbrains.kotlin.metadata.ProtoBuf; +import org.jetbrains.kotlin.name.ClassId; +import org.jetbrains.kotlin.name.FqName; +import org.jetbrains.kotlin.name.Name; +import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.psi.synthetics.SyntheticClassOrObjectDescriptor; +import org.jetbrains.kotlin.resolve.*; +import org.jetbrains.kotlin.resolve.calls.callUtil.CallUtilKt; +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall; +import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt; +import org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature; +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; +import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter; +import org.jetbrains.kotlin.resolve.scopes.MemberScope; +import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver; +import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver; +import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue; +import org.jetbrains.kotlin.serialization.DescriptorSerializer; +import org.jetbrains.kotlin.types.KotlinType; +import org.jetbrains.org.objectweb.asm.FieldVisitor; +import org.jetbrains.org.objectweb.asm.MethodVisitor; +import org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; +import org.jetbrains.org.objectweb.asm.commons.Method; + +import java.util.*; + +import static org.jetbrains.kotlin.builtins.StandardNames.ENUM_VALUES; +import static org.jetbrains.kotlin.builtins.StandardNames.ENUM_VALUE_OF; +import static org.jetbrains.kotlin.codegen.AsmUtil.CAPTURED_THIS_FIELD; +import static org.jetbrains.kotlin.codegen.CodegenUtilKt.isGenericToArray; +import static org.jetbrains.kotlin.codegen.CodegenUtilKt.isNonGenericToArray; +import static org.jetbrains.kotlin.codegen.DescriptorAsmUtil.*; +import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.*; +import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.enumEntryNeedSubclass; +import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.getDelegatedLocalVariableMetadata; +import static org.jetbrains.kotlin.load.java.DescriptorsJvmAbiUtil.*; +import static org.jetbrains.kotlin.load.java.JvmAbi.HIDDEN_INSTANCE_FIELD; +import static org.jetbrains.kotlin.load.java.JvmAbi.INSTANCE_FIELD; +import static org.jetbrains.kotlin.resolve.BindingContext.INDEXED_LVALUE_GET; +import static org.jetbrains.kotlin.resolve.BindingContext.INDEXED_LVALUE_SET; +import static org.jetbrains.kotlin.resolve.BindingContextUtils.getNotNull; +import static org.jetbrains.kotlin.resolve.DescriptorToSourceUtils.descriptorToDeclaration; +import static org.jetbrains.kotlin.resolve.DescriptorUtils.*; +import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE; +import static org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.NO_ORIGIN; +import static org.jetbrains.kotlin.types.Variance.INVARIANT; +import static org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils.isLocalFunction; +import static org.jetbrains.org.objectweb.asm.Opcodes.*; +import static org.jetbrains.org.objectweb.asm.Type.getObjectType; + +public class ImplementationBodyCodegen extends ClassBodyCodegen { + public static final String ENUM_VALUES_FIELD_NAME = "$VALUES"; + private Type superClassAsmType; + @NotNull + private SuperClassInfo superClassInfo; + private final Type classAsmType; + private final boolean isLocal; + + private List companionObjectPropertiesToCopy; + + private final DelegationFieldsInfo delegationFieldsInfo; + + private final List> additionalTasks = new ArrayList<>(); + + private final DescriptorSerializer serializer; + + private final ConstructorCodegen constructorCodegen; + + public ImplementationBodyCodegen( + @NotNull KtPureClassOrObject aClass, + @NotNull ClassContext context, + @NotNull ClassBuilder v, + @NotNull GenerationState state, + @Nullable MemberCodegen parentCodegen, + boolean isLocal + ) { + super(aClass, context, v, state, parentCodegen); + this.classAsmType = getObjectType(typeMapper.classInternalName(descriptor)); + this.isLocal = isLocal; + + this.delegationFieldsInfo = + new DelegationFieldsInfo(classAsmType, descriptor, state, bindingContext) + .getDelegationFieldsInfo(myClass.getSuperTypeListEntries()); + + JvmSerializerExtension extension = new JvmSerializerExtension(v.getSerializationBindings(), state); + this.serializer = DescriptorSerializer.create( + descriptor, extension, + parentCodegen instanceof ImplementationBodyCodegen + ? ((ImplementationBodyCodegen) parentCodegen).serializer + : DescriptorSerializer.createTopLevel(extension), + state.getProject() + ); + + this.constructorCodegen = new ConstructorCodegen( + descriptor, context, functionCodegen, this, + this, state, kind, v, classAsmType, myClass, bindingContext + ); + } + + @Override + protected void generateDeclaration() { + superClassInfo = SuperClassInfo.getSuperClassInfo(descriptor, typeMapper); + superClassAsmType = superClassInfo.getType(); + + JvmClassSignature signature = signature(); + + boolean isAbstract = false; + boolean isInterface = false; + boolean isFinal = false; + boolean isAnnotation = false; + boolean isEnum = false; + + ClassKind kind = descriptor.getKind(); + + Modality modality = descriptor.getModality(); + + if (modality == Modality.ABSTRACT || modality == Modality.SEALED) { + isAbstract = true; + } + + if (kind == ClassKind.INTERFACE) { + isAbstract = true; + isInterface = true; + } + else if (kind == ClassKind.ANNOTATION_CLASS) { + isAbstract = true; + isInterface = true; + isAnnotation = true; + } + else if (kind == ClassKind.ENUM_CLASS) { + isAbstract = hasAbstractMembers(descriptor); + isEnum = true; + } + + if (modality != Modality.OPEN && !isAbstract) { + isFinal = kind == ClassKind.OBJECT || + // Light-class mode: Do not make enum classes final since PsiClass corresponding to enum is expected to be inheritable from + !(kind == ClassKind.ENUM_CLASS && !state.getClassBuilderMode().generateBodies); + } + + int access = 0; + + if (state.getClassBuilderMode() == ClassBuilderMode.LIGHT_CLASSES && !DescriptorUtils.isTopLevelDeclaration(descriptor)) { + // !ClassBuilderMode.generateBodies means we are generating light classes & looking at a nested or inner class + // Light class generation is implemented so that Cls-classes only read bare code of classes, + // without knowing whether these classes are inner or not (see ClassStubBuilder.EMPTY_STRATEGY) + // Thus we must write full accessibility flags on inner classes in this mode + access |= getVisibilityAccessFlag(descriptor); + // Same for STATIC + if (!descriptor.isInner()) { + access |= ACC_STATIC; + } + } + else { + access |= getVisibilityAccessFlagForClass(descriptor); + } + if (isAbstract) { + access |= ACC_ABSTRACT; + } + if (isInterface) { + access |= ACC_INTERFACE; // ACC_SUPER + } + else { + access |= ACC_SUPER; + } + if (isFinal) { + access |= ACC_FINAL; + } + if (isAnnotation) { + access |= ACC_ANNOTATION; + } + if (KotlinBuiltIns.isDeprecated(descriptor)) { + access |= ACC_DEPRECATED; + } + if (isEnum) { + for (KtDeclaration declaration : myClass.getDeclarations()) { + if (declaration instanceof KtEnumEntry) { + if (enumEntryNeedSubclass(bindingContext, (KtEnumEntry) declaration)) { + access &= ~ACC_FINAL; + } + } + } + access |= ACC_ENUM; + } + + v.defineClass( + myClass.getPsiOrParent(), + state.getClassFileVersion(), + access, + signature.getName(), + signature.getJavaGenericSignature(), + signature.getSuperclassName(), + ArrayUtil.toStringArray(signature.getInterfaces()) + ); + + v.visitSource(myClass.getContainingKtFile().getName(), null); + + initDefaultSourceMappingIfNeeded(); + + writeEnclosingMethod(); + + AnnotationCodegen.forClass(v.getVisitor(), this, state).genAnnotations(descriptor, null, null); + + generateEnumEntries(); + } + + @Override + protected void generateDefaultImplsIfNeeded() { + if (isInterface(descriptor) && !isLocal) { + Type defaultImplsType = state.getTypeMapper().mapDefaultImpls(descriptor); + ClassBuilder defaultImplsBuilder = + state.getFactory().newVisitor(JvmDeclarationOriginKt.DefaultImpls(myClass.getPsiOrParent(), descriptor), defaultImplsType, myClass.getContainingKtFile()); + + CodegenContext parentContext = context.getParentContext(); + assert parentContext != null : "Parent context of interface declaration should not be null"; + + ClassContext defaultImplsContext = parentContext.intoDefaultImplsClass(descriptor, (ClassContext) context, state); + new InterfaceImplBodyCodegen(myClass, defaultImplsContext, defaultImplsBuilder, state, this).generate(); + } + } + + @Override + protected void generateErasedInlineClassIfNeeded() { + if (!(myClass instanceof KtClass)) return; + if (!InlineClassesUtilsKt.isInlineClass(descriptor)) return; + + ClassContext erasedInlineClassContext = context.intoWrapperForErasedInlineClass(descriptor, state); + new ErasedInlineClassBodyCodegen((KtClass) myClass, erasedInlineClassContext, v, state, this).generate(); + } + + @Override + protected void generateUnboxMethodForInlineClass() { + if (!(myClass instanceof KtClass)) return; + if (!InlineClassesUtilsKt.isInlineClass(descriptor)) return; + + Type ownerType = typeMapper.mapClass(descriptor); + ValueParameterDescriptor inlinedValue = InlineClassesUtilsKt.underlyingRepresentation(this.descriptor); + if (inlinedValue == null) return; + + Type valueType = typeMapper.mapType(inlinedValue.getType()); + SimpleFunctionDescriptor functionDescriptor = InlineClassDescriptorResolver.createUnboxFunctionDescriptor(this.descriptor); + assert functionDescriptor != null : "FunctionDescriptor for unbox method should be not null during codegen"; + + functionCodegen.generateMethod( + JvmDeclarationOriginKt.UnboxMethodOfInlineClass(functionDescriptor), functionDescriptor, + new FunctionGenerationStrategy.CodegenBased(state) { + @Override + public void doGenerateBody( + @NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature + ) { + InstructionAdapter iv = codegen.v; + iv.load(0, OBJECT_TYPE); + iv.getfield(ownerType.getInternalName(), inlinedValue.getName().asString(), valueType.getDescriptor()); + iv.areturn(valueType); + } + } + ); + } + + @Override + protected void generateKotlinMetadataAnnotation() { + ProtoBuf.Class classProto = serializer.classProto(descriptor).build(); + + WriteAnnotationUtilKt.writeKotlinMetadata(v, state, KotlinClassHeader.Kind.CLASS, 0, av -> { + writeAnnotationData(av, serializer, classProto); + return Unit.INSTANCE; + }); + } + + private void writeEnclosingMethod() { + // Do not emit enclosing method in "light-classes mode" since currently we generate local light classes as if they're top level + if (!state.getClassBuilderMode().generateBodies) { + return; + } + + //JVMS7: A class must have an EnclosingMethod attribute if and only if it is a local class or an anonymous class. + if (isAnonymousObject(descriptor) || !(descriptor.getContainingDeclaration() instanceof ClassOrPackageFragmentDescriptor)) { + writeOuterClassAndEnclosingMethod(); + } + } + + private static final Map KOTLIN_MARKER_INTERFACES = new HashMap<>(); + + static { + for (PlatformMutabilityMapping platformMutabilityMapping : JavaToKotlinClassMap.INSTANCE.getMutabilityMappings()) { + KOTLIN_MARKER_INTERFACES.put( + platformMutabilityMapping.getKotlinReadOnly().asSingleFqName(), + "kotlin/jvm/internal/markers/KMappedMarker"); + + ClassId mutableClassId = platformMutabilityMapping.getKotlinMutable(); + KOTLIN_MARKER_INTERFACES.put( + mutableClassId.asSingleFqName(), + "kotlin/jvm/internal/markers/K" + mutableClassId.getRelativeClassName().asString() + .replace("MutableEntry", "Entry") // kotlin.jvm.internal.markers.KMutableMap.Entry for some reason + .replace(".", "$") + ); + } + } + + @NotNull + private JvmClassSignature signature() { + return signature(descriptor, classAsmType, superClassInfo, typeMapper); + } + + @NotNull + public static JvmClassSignature signature( + @NotNull ClassDescriptor descriptor, + @NotNull Type classAsmType, + @NotNull SuperClassInfo superClassInfo, + @NotNull KotlinTypeMapper typeMapper + ) { + JvmSignatureWriter sw = new BothSignatureWriter(BothSignatureWriter.Mode.CLASS); + + typeMapper.writeFormalTypeParameters(descriptor.getDeclaredTypeParameters(), sw); + + sw.writeSuperclass(); + if (superClassInfo.getKotlinType() == null) { + sw.writeClassBegin(superClassInfo.getType()); + sw.writeClassEnd(); + } + else { + typeMapper.mapSupertype(superClassInfo.getKotlinType(), sw); + } + sw.writeSuperclassEnd(); + + LinkedHashSet superInterfaces = new LinkedHashSet<>(); + Set kotlinMarkerInterfaces = new LinkedHashSet<>(); + + for (KotlinType supertype : descriptor.getTypeConstructor().getSupertypes()) { + if (isJvmInterface(supertype.getConstructor().getDeclarationDescriptor())) { + FqName kotlinInterfaceName = DescriptorUtils.getFqName(supertype.getConstructor().getDeclarationDescriptor()).toSafe(); + + sw.writeInterface(); + Type jvmInterfaceType = typeMapper.mapSupertype(supertype, sw); + sw.writeInterfaceEnd(); + String jvmInterfaceInternalName = jvmInterfaceType.getInternalName(); + + superInterfaces.add(jvmInterfaceInternalName); + + String kotlinMarkerInterfaceInternalName = KOTLIN_MARKER_INTERFACES.get(kotlinInterfaceName); + if (kotlinMarkerInterfaceInternalName != null) { + if (typeMapper.getClassBuilderMode() == ClassBuilderMode.LIGHT_CLASSES) { + sw.writeInterface(); + Type kotlinCollectionType = typeMapper.mapType(supertype, sw, TypeMappingMode.SUPER_TYPE_KOTLIN_COLLECTIONS_AS_IS); + sw.writeInterfaceEnd(); + superInterfaces.add(kotlinCollectionType.getInternalName()); + } + + kotlinMarkerInterfaces.add(kotlinMarkerInterfaceInternalName); + } + } + } + + for (String kotlinMarkerInterface : kotlinMarkerInterfaces) { + sw.writeInterface(); + sw.writeAsmType(getObjectType(kotlinMarkerInterface)); + sw.writeInterfaceEnd(); + } + + superInterfaces.addAll(kotlinMarkerInterfaces); + + return new JvmClassSignature(classAsmType.getInternalName(), superClassInfo.getType().getInternalName(), + new ArrayList<>(superInterfaces), sw.makeJavaGenericSignature()); + } + + @Override + protected void generateSyntheticPartsBeforeBody() { + generatePropertyMetadataArrayFieldIfNeeded(classAsmType); + } + + @Override + protected void generateSyntheticPartsAfterBody() { + generateFieldForSingleton(); + + initializeObjects(); + + generateCompanionObjectBackingFieldCopies(); + + generateDelegatesToDefaultImpl(); + + generateDelegates(delegationFieldsInfo); + + generateSyntheticAccessors(); + + generateEnumMethods(); + + generateFunctionsForDataClasses(); + + generateFunctionsFromAnyForInlineClasses(); + + if (state.getClassBuilderMode() != ClassBuilderMode.LIGHT_CLASSES) { + new CollectionStubMethodGenerator(typeMapper, descriptor).generate(functionCodegen, v); + + generateToArray(); + } + + + if (context.closure != null) + genClosureFields(context.closure, v, typeMapper, state.getLanguageVersionSettings()); + + for (ExpressionCodegenExtension extension : ExpressionCodegenExtension.Companion.getInstances(state.getProject())) { + if (state.getClassBuilderMode() != ClassBuilderMode.LIGHT_CLASSES + || extension.getShouldGenerateClassSyntheticPartsInLightClassesMode() + ) { + extension.generateClassSyntheticParts(this); + } + } + } + + @Override + protected void generateConstructors() { + try { + lookupConstructorExpressionsInClosureIfPresent(); + constructorCodegen.generatePrimaryConstructor(delegationFieldsInfo, superClassAsmType); + if (!InlineClassesUtilsKt.isInlineClass(descriptor) && !(descriptor instanceof SyntheticClassOrObjectDescriptor)) { + // Synthetic classes does not have declarations for secondary constructors + for (ClassConstructorDescriptor secondaryConstructor : DescriptorUtilsKt.getSecondaryConstructors(descriptor)) { + constructorCodegen.generateSecondaryConstructor(secondaryConstructor, superClassAsmType); + } + } + } + catch (CompilationException | ProcessCanceledException e) { + throw e; + } + catch (RuntimeException e) { + throw new RuntimeException("Error generating constructors of class " + myClass.getName() + " with kind " + kind, e); + } + } + + private void generateToArray() { + if (descriptor.getKind() == ClassKind.INTERFACE) return; + + KotlinBuiltIns builtIns = DescriptorUtilsKt.getBuiltIns(descriptor); + if (!isSubclass(descriptor, builtIns.getCollection())) return; + + if (CollectionsKt.any(DescriptorUtilsKt.getAllSuperclassesWithoutAny(descriptor), + classDescriptor -> !(classDescriptor instanceof JavaClassDescriptor) && + isSubclass(classDescriptor, builtIns.getCollection()))) { + return; + } + + Collection functions = descriptor.getDefaultType().getMemberScope().getContributedFunctions( + Name.identifier("toArray"), NoLookupLocation.FROM_BACKEND + ); + boolean hasGenericToArray = false; + boolean hasNonGenericToArray = false; + for (FunctionDescriptor function : functions) { + hasGenericToArray |= isGenericToArray(function); + hasNonGenericToArray |= isNonGenericToArray(function); + } + + if (!hasNonGenericToArray) { + MethodVisitor mv = v.newMethod(NO_ORIGIN, ACC_PUBLIC, "toArray", "()[Ljava/lang/Object;", null, null); + + InstructionAdapter iv = new InstructionAdapter(mv); + mv.visitCode(); + + iv.load(0, classAsmType); + iv.invokestatic("kotlin/jvm/internal/CollectionToArray", "toArray", "(Ljava/util/Collection;)[Ljava/lang/Object;", false); + iv.areturn(Type.getType("[Ljava/lang/Object;")); + + FunctionCodegen.endVisit(mv, "toArray", myClass); + } + + if (!hasGenericToArray) { + MethodVisitor mv = v.newMethod( + NO_ORIGIN, ACC_PUBLIC, "toArray", "([Ljava/lang/Object;)[Ljava/lang/Object;", "([TT;)[TT;", null); + + InstructionAdapter iv = new InstructionAdapter(mv); + mv.visitCode(); + + iv.load(0, classAsmType); + iv.load(1, Type.getType("[Ljava/lang/Object;")); + + iv.invokestatic("kotlin/jvm/internal/CollectionToArray", "toArray", + "(Ljava/util/Collection;[Ljava/lang/Object;)[Ljava/lang/Object;", false); + iv.areturn(Type.getType("[Ljava/lang/Object;")); + + FunctionCodegen.endVisit(mv, "toArray", myClass); + } + } + + public static JvmKotlinType genPropertyOnStack( + InstructionAdapter iv, + MethodContext context, + @NotNull PropertyDescriptor propertyDescriptor, + Type classAsmType, + int index, + GenerationState state + ) { + iv.load(index, classAsmType); + if (couldUseDirectAccessToProperty(propertyDescriptor, /* forGetter = */ true, + /* isDelegated = */ false, context, state.getShouldInlineConstVals())) { + KotlinType kotlinType = propertyDescriptor.getType(); + Type type = state.getTypeMapper().mapType(kotlinType); + String fieldName = ((FieldOwnerContext) context.getParentContext()).getFieldName(propertyDescriptor, false); + iv.getfield(classAsmType.getInternalName(), fieldName, type.getDescriptor()); + return new JvmKotlinType(type, kotlinType); + } + else { + PropertyGetterDescriptor getter = propertyDescriptor.getGetter(); + + //noinspection ConstantConditions + Method method = state.getTypeMapper().mapAsmMethod(getter); + iv.invokevirtual(classAsmType.getInternalName(), method.getName(), method.getDescriptor(), false); + return new JvmKotlinType(method.getReturnType(), getter.getReturnType()); + } + } + + private void generateFunctionsForDataClasses() { + if (!descriptor.isData()) return; + if (!(myClass instanceof KtClassOrObject)) return; + new DataClassMethodGeneratorImpl((KtClassOrObject)myClass, bindingContext).generate(); + } + + private void generateFunctionsFromAnyForInlineClasses() { + if (!InlineClassesUtilsKt.isInlineClass(descriptor)) return; + if (!(myClass instanceof KtClassOrObject)) return; + new FunctionsFromAnyGeneratorImpl( + (KtClassOrObject) myClass, bindingContext, descriptor, classAsmType, context, v, state + ).generate(); + } + + private class DataClassMethodGeneratorImpl extends DataClassMethodGenerator { + private final FunctionsFromAnyGeneratorImpl functionsFromAnyGenerator; + + DataClassMethodGeneratorImpl( + KtClassOrObject klass, + BindingContext bindingContext + ) { + super(klass, bindingContext); + this.functionsFromAnyGenerator = new FunctionsFromAnyGeneratorImpl( + klass, bindingContext, descriptor, classAsmType, ImplementationBodyCodegen.this.context, v, state + ); + } + + @Override + public void generateEqualsMethod(@NotNull FunctionDescriptor function, @NotNull List properties) { + functionsFromAnyGenerator.generateEqualsMethod(function, properties); + } + + @Override + public void generateHashCodeMethod(@NotNull FunctionDescriptor function, @NotNull List properties) { + functionsFromAnyGenerator.generateHashCodeMethod(function, properties); + } + + @Override + public void generateToStringMethod(@NotNull FunctionDescriptor function, @NotNull List properties) { + functionsFromAnyGenerator.generateToStringMethod(function, properties); + } + + @Override + public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) { + PsiElement originalElement = DescriptorToSourceUtils.descriptorToDeclaration(parameter); + functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOrigin(originalElement, function), function, new FunctionGenerationStrategy() { + @Override + public void generateBody( + @NotNull MethodVisitor mv, + @NotNull FrameMap frameMap, + @NotNull JvmMethodSignature signature, + @NotNull MethodContext context, + @NotNull MemberCodegen parentCodegen + ) { + Type componentType = signature.getReturnType(); + InstructionAdapter iv = new InstructionAdapter(mv); + if (!componentType.equals(Type.VOID_TYPE)) { + PropertyDescriptor property = + bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, descriptorToDeclaration(parameter)); + assert property != null : "Property descriptor is not found for primary constructor parameter: " + parameter; + + JvmKotlinType propertyType = genPropertyOnStack( + iv, context, property, ImplementationBodyCodegen.this.classAsmType, 0, state + ); + StackValue.coerce(propertyType.getType(), componentType, iv); + } + iv.areturn(componentType); + } + + @Override + public boolean skipNotNullAssertionsForParameters() { + return false; + } + }); + } + + @Override + public void generateCopyFunction( + @NotNull FunctionDescriptor function, + @NotNull List constructorParameters + ) { + Type thisDescriptorType = typeMapper.mapType(descriptor); + + functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOriginFromPure(myClass, function), function, new FunctionGenerationStrategy() { + @Override + public void generateBody( + @NotNull MethodVisitor mv, + @NotNull FrameMap frameMap, + @NotNull JvmMethodSignature signature, + @NotNull MethodContext context, + @NotNull MemberCodegen parentCodegen + ) { + InstructionAdapter iv = new InstructionAdapter(mv); + + iv.anew(thisDescriptorType); + iv.dup(); + + ConstructorDescriptor constructor = getPrimaryConstructorOfDataClass(descriptor); + assert function.getValueParameters().size() == constructor.getValueParameters().size() : + "Number of parameters of copy function and constructor are different. " + + "Copy: " + function.getValueParameters().size() + ", " + + "constructor: " + constructor.getValueParameters().size(); + + MutableClosure closure = ImplementationBodyCodegen.this.context.closure; + if (closure != null) { + pushCapturedFieldsOnStack(iv, closure); + } + + int parameterIndex = 1; // localVariable 0 = this + for (ValueParameterDescriptor parameterDescriptor : function.getValueParameters()) { + Type type = typeMapper.mapType(parameterDescriptor.getType()); + iv.load(parameterIndex, type); + parameterIndex += type.getSize(); + } + + Method constructorAsmMethod = typeMapper.mapAsmMethod(constructor); + iv.invokespecial(thisDescriptorType.getInternalName(), "", constructorAsmMethod.getDescriptor(), false); + + iv.areturn(thisDescriptorType); + } + + @Override + public boolean skipNotNullAssertionsForParameters() { + return false; + } + + private void pushCapturedFieldsOnStack(InstructionAdapter iv, MutableClosure closure) { + ClassDescriptor captureThis = closure.getCapturedOuterClassDescriptor(); + if (captureThis != null) { + iv.load(0, classAsmType); + Type type = typeMapper.mapType(captureThis); + iv.getfield(classAsmType.getInternalName(), CAPTURED_THIS_FIELD, type.getDescriptor()); + } + + KotlinType captureReceiver = closure.getCapturedReceiverFromOuterContext(); + if (captureReceiver != null) { + iv.load(0, classAsmType); + Type type = typeMapper.mapType(captureReceiver); + String fieldName = closure.getCapturedReceiverFieldName(bindingContext, state.getLanguageVersionSettings()); + iv.getfield(classAsmType.getInternalName(), fieldName, type.getDescriptor()); + } + + for (Map.Entry entry : closure.getCaptureVariables().entrySet()) { + DeclarationDescriptor declarationDescriptor = entry.getKey(); + EnclosedValueDescriptor enclosedValueDescriptor = entry.getValue(); + StackValue capturedValue = enclosedValueDescriptor.getInstanceValue(); + Type sharedVarType = typeMapper.getSharedVarType(declarationDescriptor); + if (sharedVarType == null) { + sharedVarType = typeMapper.mapType((VariableDescriptor) declarationDescriptor); + } + capturedValue.put(sharedVarType, iv); + } + } + }); + + functionCodegen.generateDefaultIfNeeded( + context.intoFunction(function), function, OwnerKind.IMPLEMENTATION, + (valueParameter, codegen) -> { + assert ((ClassDescriptor) function.getContainingDeclaration()).isData() + : "Function container must have [data] modifier: " + function; + PropertyDescriptor property = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, valueParameter); + assert property != null : "Copy function doesn't correspond to any property: " + function; + return codegen.intermediateValueForProperty(property, false, null, StackValue.LOCAL_0); + }, + null + ); + } + } + + @NotNull + private static ConstructorDescriptor getPrimaryConstructorOfDataClass(@NotNull ClassDescriptor classDescriptor) { + ConstructorDescriptor constructor = classDescriptor.getUnsubstitutedPrimaryConstructor(); + assert constructor != null : "Data class must have primary constructor: " + classDescriptor; + return constructor; + } + + private void generateEnumMethods() { + if (isEnumClass(descriptor)) { + generateEnumValuesMethod(); + generateEnumValueOfMethod(); + } + } + + private void generateEnumValuesMethod() { + Type type = typeMapper.mapType(DescriptorUtilsKt.getBuiltIns(descriptor).getArrayType(INVARIANT, descriptor.getDefaultType())); + + FunctionDescriptor valuesFunction = + CollectionsKt.single(descriptor.getStaticScope().getContributedFunctions(ENUM_VALUES, NoLookupLocation.FROM_BACKEND)); + MethodVisitor mv = v.newMethod( + JvmDeclarationOriginKt.OtherOriginFromPure(myClass, valuesFunction), ACC_PUBLIC | ACC_STATIC, ENUM_VALUES.asString(), + "()" + type.getDescriptor(), null, null + ); + if (!state.getClassBuilderMode().generateBodies) return; + + mv.visitCode(); + mv.visitFieldInsn(GETSTATIC, classAsmType.getInternalName(), ENUM_VALUES_FIELD_NAME, type.getDescriptor()); + mv.visitMethodInsn(INVOKEVIRTUAL, type.getInternalName(), "clone", "()Ljava/lang/Object;", false); + mv.visitTypeInsn(CHECKCAST, type.getInternalName()); + mv.visitInsn(ARETURN); + FunctionCodegen.endVisit(mv, "values()", myClass); + } + + private void generateEnumValueOfMethod() { + FunctionDescriptor valueOfFunction = + CollectionsKt.single(descriptor.getStaticScope().getContributedFunctions(ENUM_VALUE_OF, NoLookupLocation.FROM_BACKEND), + DescriptorUtilsKt::isEnumValueOfMethod); + MethodVisitor mv = + v.newMethod(JvmDeclarationOriginKt.OtherOriginFromPure(myClass, valueOfFunction), ACC_PUBLIC | ACC_STATIC, ENUM_VALUE_OF.asString(), + "(Ljava/lang/String;)" + classAsmType.getDescriptor(), null, null); + if (!state.getClassBuilderMode().generateBodies) return; + + mv.visitCode(); + mv.visitLdcInsn(classAsmType); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESTATIC, "java/lang/Enum", "valueOf", "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;", false); + mv.visitTypeInsn(CHECKCAST, classAsmType.getInternalName()); + mv.visitInsn(ARETURN); + FunctionCodegen.endVisit(mv, "valueOf()", myClass); + } + + private void generateFieldForSingleton() { + if (isCompanionObjectInInterfaceNotIntrinsic(descriptor)) { + StackValue.Field field = StackValue.createSingletonViaInstance(descriptor, typeMapper, HIDDEN_INSTANCE_FIELD); + //hidden instance in interface companion + v.newField(JvmDeclarationOriginKt.OtherOrigin(descriptor), + ACC_SYNTHETIC | ACC_STATIC | ACC_FINAL, field.name, field.type.getDescriptor(), null, null); + } + + if (isEnumEntry(descriptor) || isCompanionObject(descriptor)) return; + + if (isNonCompanionObject(descriptor)) { + StackValue.Field field = StackValue.createSingletonViaInstance(descriptor, typeMapper, INSTANCE_FIELD); + FieldVisitor fv = v.newField( + JvmDeclarationOriginKt.OtherOriginFromPure(myClass), + ACC_PUBLIC | ACC_STATIC | ACC_FINAL, + field.name, field.type.getDescriptor(), null, null + ); + AnnotationCodegen.forField(fv, this, state).visitAnnotation(Type.getDescriptor(NotNull.class), false).visitEnd(); + return; + } + + ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor(); + if (companionObjectDescriptor == null) { + return; + } + + @Nullable KtObjectDeclaration companionObject = CollectionsKt.firstOrNull(myClass.getCompanionObjects()); + + int properFieldVisibilityFlag = getVisibilityAccessFlag(companionObjectDescriptor); + boolean deprecatedFieldForInvisibleCompanionObject = + state.getLanguageVersionSettings().supportsFeature(LanguageFeature.DeprecatedFieldForInvisibleCompanionObject); + boolean properVisibilityForCompanionObjectInstanceField = + state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ProperVisibilityForCompanionObjectInstanceField); + boolean hasPrivateOrProtectedProperVisibility = (properFieldVisibilityFlag & (ACC_PRIVATE | ACC_PROTECTED)) != 0; + boolean hasPackagePrivateProperVisibility = (properFieldVisibilityFlag & (ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED)) == 0; + boolean fieldShouldBeDeprecated = + deprecatedFieldForInvisibleCompanionObject && + !properVisibilityForCompanionObjectInstanceField && + (hasPrivateOrProtectedProperVisibility || hasPackagePrivateProperVisibility || + isNonIntrinsicPrivateCompanionObjectInInterface(companionObjectDescriptor)); + boolean fieldIsForcedToBePublic = + JvmCodegenUtil.isJvmInterface(descriptor) || + !properVisibilityForCompanionObjectInstanceField; + int fieldAccessFlags = ACC_STATIC | ACC_FINAL; + if (fieldIsForcedToBePublic) { + fieldAccessFlags |= ACC_PUBLIC; + } + else { + fieldAccessFlags |= properFieldVisibilityFlag; + } + if (fieldShouldBeDeprecated) { + fieldAccessFlags |= ACC_DEPRECATED; + } + if (properVisibilityForCompanionObjectInstanceField && + JvmCodegenUtil.isCompanionObjectInInterfaceNotIntrinsic(companionObjectDescriptor) && + DescriptorVisibilities.isPrivate(companionObjectDescriptor.getVisibility())) { + fieldAccessFlags |= ACC_SYNTHETIC; + } + StackValue.Field field = StackValue.singleton(companionObjectDescriptor, typeMapper); + FieldVisitor fv = v.newField( + JvmDeclarationOriginKt.OtherOrigin(companionObject == null ? myClass.getPsiOrParent() : companionObject), + fieldAccessFlags, field.name, field.type.getDescriptor(), null, null + ); + AnnotationCodegen.forField(fv, this, state).visitAnnotation(Type.getDescriptor(NotNull.class), false).visitEnd(); + if (fieldShouldBeDeprecated) { + AnnotationCodegen.forField(fv, this, state).visitAnnotation(Type.getDescriptor(Deprecated.class), true).visitEnd(); + } + } + + private void initializeObjects() { + if (!DescriptorUtils.isObject(descriptor)) return; + if (!state.getClassBuilderMode().generateBodies) return; + + boolean isNonCompanionObject = isNonCompanionObject(descriptor); + boolean isInterfaceCompanion = isCompanionObjectInInterfaceNotIntrinsic(descriptor); + boolean isInterfaceCompanionWithBackingFieldsInOuter = isInterfaceCompanionWithBackingFieldsInOuter(descriptor); + boolean isMappedIntrinsicCompanionObject = isMappedIntrinsicCompanionObject(descriptor); + boolean isClassCompanionWithBackingFieldsInOuter = isClassCompanionObjectWithBackingFieldsInOuter(descriptor); + if (isNonCompanionObject || + (isInterfaceCompanion && !isInterfaceCompanionWithBackingFieldsInOuter) || + isMappedIntrinsicCompanionObject + ) { + ExpressionCodegen clInitCodegen = createOrGetClInitCodegen(); + InstructionAdapter v = clInitCodegen.v; + markLineNumberForElement(element.getPsiOrParent(), v); + v.anew(classAsmType); + v.dup(); + v.invokespecial(classAsmType.getInternalName(), "", "()V", false); + + //local0 emulates this in object constructor + int local0Index = clInitCodegen.getFrameMap().enterTemp(classAsmType); + assert local0Index == 0 : "Local variable with index 0 in clInit should be used only for singleton instance keeping"; + StackValue.Local local0 = StackValue.local(0, classAsmType); + local0.store(StackValue.onStack(classAsmType), clInitCodegen.v); + StackValue.Field singleton = + StackValue.createSingletonViaInstance( + descriptor, typeMapper, isInterfaceCompanion ? HIDDEN_INSTANCE_FIELD : INSTANCE_FIELD + ); + singleton.store(local0, clInitCodegen.v); + + generateInitializers(clInitCodegen); + + if (isInterfaceCompanion) { + //initialize singleton instance in outer by hidden instance + StackValue.singleton(descriptor, typeMapper).store( + singleton, getParentCodegen().createOrGetClInitCodegen().v, true + ); + } + } + else if (isClassCompanionWithBackingFieldsInOuter || isInterfaceCompanionWithBackingFieldsInOuter) { + ImplementationBodyCodegen parentCodegen = (ImplementationBodyCodegen) getParentCodegen(); + ExpressionCodegen parentClInitCodegen = parentCodegen.createOrGetClInitCodegen(); + InstructionAdapter parentVisitor = parentClInitCodegen.v; + + FunctionDescriptor constructor = (FunctionDescriptor) parentCodegen.context.accessibleDescriptor( + CollectionsKt.single(descriptor.getConstructors()), /* superCallExpression = */ null + ); + parentCodegen.generateMethodCallTo(constructor, null, parentVisitor); + StackValue instance = StackValue.onStack(parentCodegen.typeMapper.mapClass(descriptor)); + StackValue.singleton(descriptor, parentCodegen.typeMapper).store(instance, parentVisitor, true); + + generateInitializers(parentClInitCodegen); + } + else { + assert false : "Unknown object type: " + descriptor; + } + } + + private static boolean isInterfaceCompanionWithBackingFieldsInOuter(@NotNull DeclarationDescriptor declarationDescriptor) { + DeclarationDescriptor interfaceClass = declarationDescriptor.getContainingDeclaration(); + if (!isCompanionObject(declarationDescriptor) || !isJvmInterface(interfaceClass)) return false; + + Collection descriptors = ((ClassDescriptor) declarationDescriptor).getUnsubstitutedMemberScope() + .getContributedDescriptors(DescriptorKindFilter.ALL, MemberScope.Companion.getALL_NAME_FILTER()); + return CollectionsKt.any(descriptors, d -> d instanceof PropertyDescriptor && hasJvmFieldAnnotation((PropertyDescriptor) d)); + } + + private void generateCompanionObjectBackingFieldCopies() { + if (companionObjectPropertiesToCopy == null || companionObjectPropertiesToCopy.isEmpty()) return; + + boolean isPrivateCompanion = + DescriptorVisibilities.isPrivate( + ((ClassDescriptor) companionObjectPropertiesToCopy.get(0).descriptor.getContainingDeclaration()).getVisibility()); + + int modifiers = ACC_STATIC | ACC_FINAL | ACC_PUBLIC | (isPrivateCompanion ? ACC_DEPRECATED : 0); + List additionalVisibleAnnotations = + isPrivateCompanion ? Collections.singletonList(CodegenUtilKt.JAVA_LANG_DEPRECATED) : Collections.emptyList(); + for (PropertyAndDefaultValue info : companionObjectPropertiesToCopy) { + PropertyDescriptor property = info.descriptor; + + Type type = typeMapper.mapType(property); + + FieldVisitor fv = v.newField(JvmDeclarationOriginKt.Synthetic(DescriptorToSourceUtils.descriptorToDeclaration(property), property), + modifiers, context.getFieldName(property, false), + type.getDescriptor(), typeMapper.mapFieldSignature(property.getType(), property), + info.defaultValue); + + AnnotationCodegen.forField(fv, this, state).genAnnotations(property, type, null, null, additionalVisibleAnnotations); + + //This field are always static and final so if it has constant initializer don't do anything in clinit, + //field would be initialized via default value in v.newField(...) - see JVM SPEC Ch.4 + // TODO: test this code + if (state.getClassBuilderMode().generateBodies && info.defaultValue == null) { + ExpressionCodegen codegen = createOrGetClInitCodegen(); + int companionObjectIndex = putCompanionObjectInLocalVar(codegen); + StackValue.local(companionObjectIndex, OBJECT_TYPE).put(codegen.v); + copyFieldFromCompanionObject(property); + } + } + } + + private int putCompanionObjectInLocalVar(ExpressionCodegen codegen) { + FrameMap frameMap = codegen.myFrameMap; + ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor(); + int companionObjectIndex = frameMap.getIndex(companionObjectDescriptor); + if (companionObjectIndex == -1) { + companionObjectIndex = frameMap.enter(companionObjectDescriptor, OBJECT_TYPE); + StackValue companionObject = StackValue.singleton(companionObjectDescriptor, typeMapper); + StackValue.local(companionObjectIndex, companionObject.type).store(companionObject, codegen.v); + } + return companionObjectIndex; + } + + private void copyFieldFromCompanionObject(PropertyDescriptor propertyDescriptor) { + ExpressionCodegen codegen = createOrGetClInitCodegen(); + StackValue property = codegen.intermediateValueForProperty(propertyDescriptor, false, null, StackValue.none()); + StackValue.Field field = StackValue.field( + property.type, property.kotlinType, classAsmType, propertyDescriptor.getName().asString(), + true, StackValue.none(), propertyDescriptor + ); + field.store(property, codegen.v); + } + + public void generateInitializers(@NotNull ExpressionCodegen codegen) { + generateInitializers(() -> codegen); + } + + private void lookupConstructorExpressionsInClosureIfPresent() { + if (!state.getClassBuilderMode().generateBodies || descriptor.getConstructors().isEmpty()) return; + + KtVisitorVoid visitor = new KtVisitorVoid() { + @Override + public void visitKtElement(@NotNull KtElement e) { + e.acceptChildren(this); + } + + @Override + public void visitSimpleNameExpression(@NotNull KtSimpleNameExpression expr) { + DeclarationDescriptor descriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, expr); + + if (isLocalFunction(descriptor)) { + lookupInContext(descriptor); + } + else if (descriptor instanceof CallableMemberDescriptor) { + ResolvedCall call = CallUtilKt.getResolvedCall(expr, bindingContext); + if (call != null) { + lookupReceivers(call); + } + if (call instanceof VariableAsFunctionResolvedCall) { + lookupReceivers(((VariableAsFunctionResolvedCall) call).getVariableCall()); + } + } + else if (descriptor instanceof VariableDescriptor) { + DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); + if (containingDeclaration instanceof ConstructorDescriptor) { + ClassDescriptor classDescriptor = ((ConstructorDescriptor) containingDeclaration).getConstructedClass(); + if (classDescriptor == ImplementationBodyCodegen.this.descriptor) return; + } + if (lookupInContext(descriptor)) { + if (isDelegatedLocalVariable(descriptor)) { + VariableDescriptor metadata = getDelegatedLocalVariableMetadata((VariableDescriptor) descriptor, bindingContext); + lookupInContext(metadata); + } + } + } + } + + private void lookupReceivers(@NotNull ResolvedCall call) { + lookupReceiver(call.getDispatchReceiver()); + lookupReceiver(call.getExtensionReceiver()); + } + + private void lookupReceiver(@Nullable ReceiverValue value) { + if (value instanceof ImplicitReceiver) { + if (value instanceof ExtensionReceiver) { + ReceiverParameterDescriptor parameter = + ((ExtensionReceiver) value).getDeclarationDescriptor().getExtensionReceiverParameter(); + assert parameter != null : "Extension receiver should exist: " + ((ExtensionReceiver) value).getDeclarationDescriptor(); + lookupInContext(parameter); + } + else { + lookupInContext(((ImplicitReceiver) value).getDeclarationDescriptor()); + } + } + } + + private boolean lookupInContext(@NotNull DeclarationDescriptor toLookup) { + return context.lookupInContext(toLookup, StackValue.LOCAL_0, state, true) != null; + } + + @Override + public void visitThisExpression(@NotNull KtThisExpression expression) { + DeclarationDescriptor descriptor = bindingContext.get(BindingContext.REFERENCE_TARGET, expression.getInstanceReference()); + assert descriptor instanceof CallableDescriptor || + descriptor instanceof ClassDescriptor : "'This' reference target should be class or callable descriptor but was " + descriptor; + if (descriptor instanceof ClassDescriptor) { + lookupInContext(descriptor); + } + + if (descriptor instanceof CallableDescriptor) { + ReceiverParameterDescriptor parameter = ((CallableDescriptor) descriptor).getExtensionReceiverParameter(); + if (parameter != null) { + lookupInContext(parameter); + } + } + } + + @Override + public void visitSuperExpression(@NotNull KtSuperExpression expression) { + lookupInContext(ExpressionCodegen.getSuperCallLabelTarget(context, expression)); + } + + @Override + public void visitArrayAccessExpression(@NotNull KtArrayAccessExpression expression) { + ResolvedCall resolvedGetCall = bindingContext.get(INDEXED_LVALUE_GET, expression); + if (resolvedGetCall != null) { + ReceiverValue receiver = resolvedGetCall.getDispatchReceiver(); + lookupReceiver(receiver); + } + + ResolvedCall resolvedSetCall = bindingContext.get(INDEXED_LVALUE_SET, expression); + if (resolvedSetCall != null) { + ReceiverValue receiver = resolvedSetCall.getDispatchReceiver(); + lookupReceiver(receiver); + } + super.visitArrayAccessExpression(expression); + } + }; + + for (KtDeclaration declaration : myClass.getDeclarations()) { + if (declaration instanceof KtProperty) { + KtProperty property = (KtProperty) declaration; + KtExpression initializer = property.getDelegateExpressionOrInitializer(); + if (initializer != null) { + initializer.accept(visitor); + } + } + else if (declaration instanceof KtAnonymousInitializer) { + KtAnonymousInitializer initializer = (KtAnonymousInitializer) declaration; + initializer.accept(visitor); + } + else if (declaration instanceof KtSecondaryConstructor) { + KtSecondaryConstructor constructor = (KtSecondaryConstructor) declaration; + constructor.accept(visitor); + } + } + + for (KtSuperTypeListEntry specifier : myClass.getSuperTypeListEntries()) { + if (specifier instanceof KtDelegatedSuperTypeEntry) { + KtExpression delegateExpression = ((KtDelegatedSuperTypeEntry) specifier).getDelegateExpression(); + assert delegateExpression != null; + delegateExpression.accept(visitor); + } + else if (specifier instanceof KtSuperTypeCallEntry) { + specifier.accept(visitor); + } + } + } + + private void generateEnumEntries() { + if (descriptor.getKind() != ClassKind.ENUM_CLASS) return; + + List enumEntries = CollectionsKt.filterIsInstance(element.getDeclarations(), KtEnumEntry.class); + + for (KtEnumEntry enumEntry : enumEntries) { + ClassDescriptor descriptor = getNotNull(bindingContext, BindingContext.CLASS, enumEntry); + int isDeprecated = KotlinBuiltIns.isDeprecated(descriptor) ? ACC_DEPRECATED : 0; + FieldVisitor fv = v.newField(JvmDeclarationOriginKt.OtherOrigin(enumEntry, descriptor), ACC_PUBLIC | ACC_ENUM | ACC_STATIC | ACC_FINAL | isDeprecated, + descriptor.getName().asString(), classAsmType.getDescriptor(), null, null); + AnnotationCodegen.forField(fv, this, state).genAnnotations(descriptor, null, null); + } + + initializeEnumConstants(enumEntries); + } + + private void initializeEnumConstants(@NotNull List enumEntries) { + if (!state.getClassBuilderMode().generateBodies) return; + + ExpressionCodegen codegen = createOrGetClInitCodegen(); + InstructionAdapter iv = codegen.v; + + Type arrayAsmType = typeMapper.mapType(DescriptorUtilsKt.getBuiltIns(descriptor).getArrayType(INVARIANT, descriptor.getDefaultType())); + v.newField(JvmDeclarationOriginKt.OtherOriginFromPure(myClass), ACC_PRIVATE | ACC_STATIC | ACC_FINAL | ACC_SYNTHETIC, ENUM_VALUES_FIELD_NAME, + arrayAsmType.getDescriptor(), null, null); + + iv.iconst(enumEntries.size()); + iv.newarray(classAsmType); + + if (!enumEntries.isEmpty()) { + iv.dup(); + for (int ordinal = 0, size = enumEntries.size(); ordinal < size; ordinal++) { + initializeEnumConstant(enumEntries, ordinal); + } + } + + iv.putstatic(classAsmType.getInternalName(), ENUM_VALUES_FIELD_NAME, arrayAsmType.getDescriptor()); + } + + private void initializeEnumConstant(@NotNull List enumEntries, int ordinal) { + ExpressionCodegen codegen = createOrGetClInitCodegen(); + InstructionAdapter iv = codegen.v; + KtEnumEntry enumEntry = enumEntries.get(ordinal); + + iv.dup(); + iv.iconst(ordinal); + + ClassDescriptor classDescriptor = getNotNull(bindingContext, BindingContext.CLASS, enumEntry); + Type implClass = typeMapper.mapClass(classDescriptor); + + iv.anew(implClass); + iv.dup(); + + iv.aconst(enumEntry.getName()); + iv.iconst(ordinal); + + List delegationSpecifiers = enumEntry.getSuperTypeListEntries(); + ResolvedCall defaultArgumentsConstructorCall = CallUtilKt.getResolvedCall(enumEntry, bindingContext); + boolean enumEntryHasSubclass = CodegenBinding.enumEntryNeedSubclass(bindingContext, classDescriptor); + if (delegationSpecifiers.size() == 1 && !enumEntryNeedSubclass(bindingContext, enumEntry)) { + ResolvedCall resolvedCall = CallUtilKt.getResolvedCallWithAssert(delegationSpecifiers.get(0), bindingContext); + + CallableMethod method = typeMapper.mapToCallableMethod((ConstructorDescriptor) resolvedCall.getResultingDescriptor(), false); + + codegen.invokeMethodWithArguments(method, resolvedCall, StackValue.none()); + } + else if (defaultArgumentsConstructorCall != null && !enumEntryHasSubclass) { + codegen.invokeFunction(defaultArgumentsConstructorCall, StackValue.none()).put(Type.VOID_TYPE, iv); + } + else { + iv.invokespecial(implClass.getInternalName(), "", "(Ljava/lang/String;I)V", false); + } + + iv.dup(); + iv.putstatic(classAsmType.getInternalName(), enumEntry.getName(), classAsmType.getDescriptor()); + iv.astore(OBJECT_TYPE); + } + + private void generateDelegates(DelegationFieldsInfo delegationFieldsInfo) { + for (KtSuperTypeListEntry specifier : myClass.getSuperTypeListEntries()) { + if (specifier instanceof KtDelegatedSuperTypeEntry) { + DelegationFieldsInfo.Field field = delegationFieldsInfo.getInfo((KtDelegatedSuperTypeEntry) specifier); + if (field == null) continue; + + generateDelegateField(field); + KtExpression delegateExpression = ((KtDelegatedSuperTypeEntry) specifier).getDelegateExpression(); + KotlinType delegateExpressionType = delegateExpression != null ? bindingContext.getType(delegateExpression) : null; + ClassDescriptor superClass = JvmCodegenUtil.getSuperClass(specifier, state, bindingContext); + if (superClass == null) continue; + + generateDelegates(superClass, delegateExpressionType, field); + } + } + } + + private void generateDelegateField(DelegationFieldsInfo.Field fieldInfo) { + if (!fieldInfo.generateField) return; + + v.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE | ACC_FINAL | ACC_SYNTHETIC, + fieldInfo.name, fieldInfo.type.getDescriptor(), fieldInfo.genericSignature, null); + } + + private void generateDelegates( + @NotNull ClassDescriptor toInterface, + @Nullable KotlinType delegateExpressionType, + @NotNull DelegationFieldsInfo.Field field + ) { + for (Map.Entry entry : DelegationResolver.Companion.getDelegates( + descriptor, toInterface, delegateExpressionType).entrySet() + ) { + CallableMemberDescriptor member = entry.getKey(); + CallableMemberDescriptor delegateTo = entry.getValue(); + if (member instanceof PropertyDescriptor) { + propertyCodegen.genDelegate((PropertyDescriptor) member, (PropertyDescriptor) delegateTo, field.getStackValue()); + } + else if (member instanceof FunctionDescriptor) { + functionCodegen.genDelegate((FunctionDescriptor) member, (FunctionDescriptor) delegateTo, field.getStackValue()); + } + } + } + + public void addCompanionObjectPropertyToCopy(@NotNull PropertyDescriptor descriptor, Object defaultValue) { + if (companionObjectPropertiesToCopy == null) { + companionObjectPropertiesToCopy = new ArrayList<>(); + } + companionObjectPropertiesToCopy.add(new PropertyAndDefaultValue(descriptor, defaultValue)); + } + + @Override + protected void done() { + for (Function2 task : additionalTasks) { + task.invoke(this, v); + } + + super.done(); + } + + private static class PropertyAndDefaultValue { + public final PropertyDescriptor descriptor; + public final Object defaultValue; + + public PropertyAndDefaultValue(@NotNull PropertyDescriptor descriptor, Object defaultValue) { + this.descriptor = descriptor; + this.defaultValue = defaultValue; + } + } + + public void addAdditionalTask(Function2 additionalTask) { + additionalTasks.add(additionalTask); + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java.201 b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java.201 new file mode 100644 index 00000000000..25b7285eb81 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/PropertyCodegen.java.201 @@ -0,0 +1,680 @@ +/* + * Copyright 2000-2018 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 com.intellij.psi.PsiElement; +import kotlin.Pair; +import kotlin.collections.CollectionsKt; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.codegen.context.CodegenContextUtil; +import org.jetbrains.kotlin.codegen.context.FieldOwnerContext; +import org.jetbrains.kotlin.codegen.context.MultifileClassFacadeContext; +import org.jetbrains.kotlin.codegen.context.MultifileClassPartContext; +import org.jetbrains.kotlin.codegen.state.GenerationState; +import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.config.LanguageFeature; +import org.jetbrains.kotlin.descriptors.*; +import org.jetbrains.kotlin.descriptors.annotations.Annotations; +import org.jetbrains.kotlin.load.java.DescriptorsJvmAbiUtil; +import org.jetbrains.kotlin.load.java.JvmAbi; +import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.resolve.BindingContext; +import org.jetbrains.kotlin.resolve.DescriptorFactory; +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; +import org.jetbrains.kotlin.resolve.InlineClassesUtilsKt; +import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall; +import org.jetbrains.kotlin.resolve.calls.util.UnderscoreUtilKt; +import org.jetbrains.kotlin.resolve.constants.ConstantValue; +import org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt; +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOriginKt; +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature; +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature; +import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor; +import org.jetbrains.kotlin.types.ErrorUtils; +import org.jetbrains.kotlin.types.KotlinType; +import org.jetbrains.org.objectweb.asm.FieldVisitor; +import org.jetbrains.org.objectweb.asm.MethodVisitor; +import org.jetbrains.org.objectweb.asm.Opcodes; +import org.jetbrains.org.objectweb.asm.Type; +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; +import org.jetbrains.org.objectweb.asm.commons.Method; + +import java.util.Collections; +import java.util.List; + +import static org.jetbrains.kotlin.codegen.DescriptorAsmUtil.getDeprecatedAccessFlag; +import static org.jetbrains.kotlin.codegen.DescriptorAsmUtil.getVisibilityForBackingField; +import static org.jetbrains.kotlin.codegen.FunctionCodegen.processInterfaceMethod; +import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isConstOrHasJvmFieldAnnotation; +import static org.jetbrains.kotlin.codegen.JvmCodegenUtil.isJvmInterface; +import static org.jetbrains.kotlin.codegen.binding.CodegenBinding.*; +import static org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings.*; +import static org.jetbrains.kotlin.diagnostics.Errors.EXPECTED_FUNCTION_SOURCE_WITH_DEFAULT_ARGUMENTS_NOT_FOUND; +import static org.jetbrains.kotlin.fileClasses.JvmFileClassUtilKt.isTopLevelInJvmMultifileClass; +import static org.jetbrains.kotlin.resolve.DescriptorUtils.isCompanionObject; +import static org.jetbrains.kotlin.resolve.DescriptorUtils.isInterface; +import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.K_PROPERTY_TYPE; +import static org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt.hasJvmFieldAnnotation; +import static org.jetbrains.kotlin.resolve.jvm.annotations.JvmAnnotationUtilKt.hasJvmSyntheticAnnotation; +import static org.jetbrains.org.objectweb.asm.Opcodes.*; + +public class PropertyCodegen { + private final GenerationState state; + private final ClassBuilder v; + private final FunctionCodegen functionCodegen; + private final KotlinTypeMapper typeMapper; + private final BindingContext bindingContext; + private final FieldOwnerContext context; + private final MemberCodegen memberCodegen; + private final OwnerKind kind; + + public PropertyCodegen( + @NotNull FieldOwnerContext context, + @NotNull ClassBuilder v, + @NotNull FunctionCodegen functionCodegen, + @NotNull MemberCodegen memberCodegen + ) { + this.state = functionCodegen.state; + this.v = v; + this.functionCodegen = functionCodegen; + this.typeMapper = state.getTypeMapper(); + this.bindingContext = state.getBindingContext(); + this.context = context; + this.memberCodegen = memberCodegen; + this.kind = context.getContextKind(); + } + + public void gen(@NotNull KtProperty property) { + VariableDescriptor variableDescriptor = bindingContext.get(BindingContext.VARIABLE, property); + if (!(variableDescriptor instanceof PropertyDescriptor)) { + throw ExceptionLogger.logDescriptorNotFound( + "Property " + property.getName() + " should have a property descriptor: " + variableDescriptor, property + ); + } + + PropertyDescriptor propertyDescriptor = (PropertyDescriptor) variableDescriptor; + gen(property, propertyDescriptor, property.getGetter(), property.getSetter()); + } + + public void genDestructuringDeclaration(@NotNull KtDestructuringDeclarationEntry entry) { + VariableDescriptor variableDescriptor = bindingContext.get(BindingContext.VARIABLE, entry); + if (!(variableDescriptor instanceof PropertyDescriptor)) { + throw ExceptionLogger.logDescriptorNotFound( + "Destructuring declaration entry" + entry.getName() + " should have a property descriptor: " + variableDescriptor, entry + ); + } + + if (!UnderscoreUtilKt.isSingleUnderscore(entry)) { + genDestructuringDeclaration((PropertyDescriptor) variableDescriptor); + } + } + + public void generateInPackageFacade(@NotNull DeserializedPropertyDescriptor deserializedProperty) { + assert context instanceof MultifileClassFacadeContext : "should be called only for generating facade: " + context; + + genBackingFieldAndAnnotations(deserializedProperty); + + if (!isConstOrHasJvmFieldAnnotation(deserializedProperty)) { + generateGetter(deserializedProperty, null); + generateSetter(deserializedProperty, null); + } + } + + private void gen( + @NotNull KtProperty declaration, + @NotNull PropertyDescriptor descriptor, + @Nullable KtPropertyAccessor getter, + @Nullable KtPropertyAccessor setter + ) { + assert kind == OwnerKind.PACKAGE || kind == OwnerKind.IMPLEMENTATION || + kind == OwnerKind.DEFAULT_IMPLS || kind == OwnerKind.ERASED_INLINE_CLASS + : "Generating property with a wrong kind (" + kind + "): " + descriptor; + + genBackingFieldAndAnnotations(descriptor); + + boolean isDefaultGetterAndSetter = isDefaultAccessor(getter) && isDefaultAccessor(setter); + + if (isAccessorNeeded(descriptor, getter, isDefaultGetterAndSetter)) { + generateGetter(descriptor, getter); + } + if (isAccessorNeeded(descriptor, setter, isDefaultGetterAndSetter)) { + generateSetter(descriptor, setter); + } + } + + private static boolean isDefaultAccessor(@Nullable KtPropertyAccessor accessor) { + return accessor == null || !accessor.hasBody(); + } + + private void genDestructuringDeclaration(@NotNull PropertyDescriptor descriptor) { + assert kind == OwnerKind.PACKAGE || kind == OwnerKind.IMPLEMENTATION || kind == OwnerKind.DEFAULT_IMPLS + : "Generating property with a wrong kind (" + kind + "): " + descriptor; + + genBackingFieldAndAnnotations(descriptor); + + generateGetter(descriptor, null); + generateSetter(descriptor, null); + } + + private void genBackingFieldAndAnnotations(@NotNull PropertyDescriptor descriptor) { + // Fields and '$annotations' methods for non-private const properties are generated in the multi-file facade + boolean isBackingFieldOwner = descriptor.isConst() && !DescriptorVisibilities.isPrivate(descriptor.getVisibility()) + ? !(context instanceof MultifileClassPartContext) + : CodegenContextUtil.isImplementationOwner(context, descriptor); + + generateBackingField(descriptor, isBackingFieldOwner); + generateSyntheticMethodIfNeeded(descriptor, isBackingFieldOwner); + } + + private boolean isAccessorNeeded( + @NotNull PropertyDescriptor descriptor, + @Nullable KtPropertyAccessor accessor, + boolean isDefaultGetterAndSetter + ) { + return isAccessorNeeded(descriptor, accessor, isDefaultGetterAndSetter, kind); + } + + public static boolean isReferenceablePropertyWithGetter(@NotNull PropertyDescriptor descriptor) { + PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(descriptor); + KtDeclaration ktDeclaration = psiElement instanceof KtDeclaration ? (KtDeclaration) psiElement : null; + if (ktDeclaration instanceof KtProperty) { + KtProperty ktProperty = (KtProperty) ktDeclaration; + boolean isDefaultGetterAndSetter = + isDefaultAccessor(ktProperty.getGetter()) && isDefaultAccessor(ktProperty.getSetter()); + return isAccessorNeeded(descriptor, ktProperty.getGetter(), isDefaultGetterAndSetter, OwnerKind.IMPLEMENTATION); + } else if (ktDeclaration instanceof KtParameter) { + return isAccessorNeeded(descriptor, null, true, OwnerKind.IMPLEMENTATION); + } else { + return isAccessorNeeded(descriptor, null, false, OwnerKind.IMPLEMENTATION); + } + } + + /** + * Determines if it's necessary to generate an accessor to the property, i.e. if this property can be referenced via getter/setter + * for any reason + * + * @see JvmCodegenUtil#couldUseDirectAccessToProperty + */ + private static boolean isAccessorNeeded( + @NotNull PropertyDescriptor descriptor, + @Nullable KtPropertyAccessor accessor, + boolean isDefaultGetterAndSetter, + OwnerKind kind + ) { + if (isConstOrHasJvmFieldAnnotation(descriptor)) return false; + + boolean isDefaultAccessor = isDefaultAccessor(accessor); + + // Don't generate accessors for interface properties with default accessors in DefaultImpls + if (kind == OwnerKind.DEFAULT_IMPLS && isDefaultAccessor) return false; + + // Delegated or extension properties can only be referenced via accessors + if (descriptor.isDelegated() || descriptor.getExtensionReceiverParameter() != null) return true; + + // Companion object properties should have accessors for non-private properties because these properties can be referenced + // via getter/setter. But these accessors getter/setter are not required for private properties that have a default getter + // and setter, in this case, the property can always be accessed through the accessor 'access$cp' and avoid some + // useless indirection by using others accessors. + if (isCompanionObject(descriptor.getContainingDeclaration())) { + if (DescriptorVisibilities.isPrivate(descriptor.getVisibility()) && isDefaultGetterAndSetter) { + return false; + } + return true; + } + + // Non-const properties from multifile classes have accessors regardless of visibility + if (isTopLevelInJvmMultifileClass(descriptor)) return true; + + // Private class properties have accessors only in cases when those accessors are non-trivial + if (DescriptorVisibilities.isPrivate(descriptor.getVisibility())) { + return !isDefaultAccessor; + } + + // Non-private properties with private setter should not be generated for trivial properties + // as the class will use direct field access instead + //noinspection ConstantConditions + if (accessor != null && accessor.isSetter() && DescriptorVisibilities.isPrivate(descriptor.getSetter().getVisibility())) { + return !isDefaultAccessor; + } + + // Non-public API (private and internal) primary vals of inline classes don't have getter + if (InlineClassesUtilsKt.isUnderlyingPropertyOfInlineClass(descriptor) && !descriptor.getVisibility().isPublicAPI()) { + return false; + } + + return true; + } + + private static boolean areAccessorsNeededForPrimaryConstructorProperty( + @NotNull PropertyDescriptor descriptor, + @NotNull OwnerKind kind + ) { + if (hasJvmFieldAnnotation(descriptor)) return false; + if (kind == OwnerKind.ERASED_INLINE_CLASS) return false; + + DescriptorVisibility visibility = descriptor.getVisibility(); + if (InlineClassesUtilsKt.isInlineClass(descriptor.getContainingDeclaration())) { + return visibility.isPublicAPI(); + } + else { + return !DescriptorVisibilities.isPrivate(visibility); + } + } + + public void generatePrimaryConstructorProperty(@NotNull PropertyDescriptor descriptor) { + genBackingFieldAndAnnotations(descriptor); + + if (areAccessorsNeededForPrimaryConstructorProperty(descriptor, context.getContextKind())) { + generateGetter(descriptor, null); + generateSetter(descriptor, null); + } + } + + public void generateConstructorPropertyAsMethodForAnnotationClass( + @NotNull KtParameter parameter, + @NotNull PropertyDescriptor descriptor, + @Nullable FunctionDescriptor expectedAnnotationConstructor + ) { + JvmMethodGenericSignature signature = typeMapper.mapAnnotationParameterSignature(descriptor); + Method asmMethod = signature.getAsmMethod(); + MethodVisitor mv = v.newMethod( + JvmDeclarationOriginKt.OtherOrigin(parameter, descriptor), + ACC_PUBLIC | ACC_ABSTRACT, + asmMethod.getName(), + asmMethod.getDescriptor(), + signature.getGenericsSignature(), + null + ); + + PropertyGetterDescriptor getter = descriptor.getGetter(); + assert getter != null : "Annotation property should have a getter: " + descriptor; + v.getSerializationBindings().put(METHOD_FOR_FUNCTION, getter, asmMethod); + AnnotationCodegen.forMethod(mv, memberCodegen, state).genAnnotations(getter, asmMethod.getReturnType(), null); + + KtExpression defaultValue = loadAnnotationArgumentDefaultValue(parameter, descriptor, expectedAnnotationConstructor); + if (defaultValue != null) { + ConstantValue constant = ExpressionCodegen.getCompileTimeConstant( + defaultValue, bindingContext, true, state.getShouldInlineConstVals()); + assert !state.getClassBuilderMode().generateBodies || constant != null + : "Default value for annotation parameter should be compile time value: " + defaultValue.getText(); + if (constant != null) { + AnnotationCodegen annotationCodegen = AnnotationCodegen.forAnnotationDefaultValue(mv, memberCodegen, state); + annotationCodegen.generateAnnotationDefaultValue(constant, descriptor.getType()); + } + } + + mv.visitEnd(); + } + + private KtExpression loadAnnotationArgumentDefaultValue( + @NotNull KtParameter ktParameter, + @NotNull PropertyDescriptor descriptor, + @Nullable FunctionDescriptor expectedAnnotationConstructor + ) { + KtExpression value = ktParameter.getDefaultValue(); + if (value != null) return value; + + if (expectedAnnotationConstructor != null) { + ValueParameterDescriptor expectedParameter = CollectionsKt.single( + expectedAnnotationConstructor.getValueParameters(), parameter -> parameter.getName().equals(descriptor.getName()) + ); + PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(expectedParameter); + if (!(element instanceof KtParameter)) { + state.getDiagnostics().report(EXPECTED_FUNCTION_SOURCE_WITH_DEFAULT_ARGUMENTS_NOT_FOUND.on(ktParameter)); + return null; + } + return ((KtParameter) element).getDefaultValue(); + } + + return null; + } + + private void generateBackingField(@NotNull PropertyDescriptor descriptor, boolean isBackingFieldOwner) { + if (isJvmInterface(descriptor.getContainingDeclaration()) || kind == OwnerKind.DEFAULT_IMPLS || + kind == OwnerKind.ERASED_INLINE_CLASS) { + return; + } + + Object defaultValue; + if (descriptor.isDelegated()) { + defaultValue = null; + } + else if (Boolean.TRUE.equals(bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor))) { + if (shouldWriteFieldInitializer(descriptor)) { + ConstantValue initializer = descriptor.getCompileTimeInitializer(); + defaultValue = initializer == null ? null : initializer.getValue(); + } + else { + defaultValue = null; + } + } + else { + return; + } + + generateBackingField(descriptor, descriptor.isDelegated(), defaultValue, isBackingFieldOwner); + } + + // Annotations on properties are stored in bytecode on an empty synthetic method. This way they're still + // accessible via reflection, and 'deprecated' and 'synthetic' flags prevent this method from being called accidentally + private void generateSyntheticMethodIfNeeded(@NotNull PropertyDescriptor descriptor, boolean isBackingFieldOwner) { + Annotations annotations = descriptor.getAnnotations(); + if (annotations.isEmpty()) return; + + Method signature = typeMapper.mapSyntheticMethodForPropertyAnnotations(descriptor); + if (kind != OwnerKind.DEFAULT_IMPLS && CodegenContextUtil.isImplementationOwner(context, descriptor)) { + v.getSerializationBindings().put(SYNTHETIC_METHOD_FOR_PROPERTY, descriptor, signature); + } + + if (isBackingFieldOwner) { + if (!isInterface(context.getContextDescriptor()) || + processInterfaceMethod(descriptor, kind, false, true, state.getJvmDefaultMode())) { + memberCodegen.generateSyntheticAnnotationsMethod(descriptor, signature, annotations); + } + } + } + + private void generateBackingField( + @NotNull PropertyDescriptor propertyDescriptor, + boolean isDelegate, + @Nullable Object defaultValue, + boolean isBackingFieldOwner + ) { + FieldDescriptor annotatedField = isDelegate ? propertyDescriptor.getDelegateField() : propertyDescriptor.getBackingField(); + + int modifiers = getDeprecatedAccessFlag(propertyDescriptor); + + for (AnnotationCodegen.JvmFlagAnnotation flagAnnotation : AnnotationCodegen.FIELD_FLAGS) { + modifiers |= flagAnnotation.getJvmFlag(annotatedField); + } + + if (kind == OwnerKind.PACKAGE) { + modifiers |= ACC_STATIC; + } + + if (!propertyDescriptor.isLateInit() && (!propertyDescriptor.isVar() || isDelegate)) { + modifiers |= ACC_FINAL; + } + + if (hasJvmSyntheticAnnotation(propertyDescriptor)) { + modifiers |= ACC_SYNTHETIC; + } + + KotlinType kotlinType = isDelegate ? getDelegateTypeForProperty(propertyDescriptor, bindingContext) : propertyDescriptor.getType(); + Type type = typeMapper.mapType(kotlinType); + + ClassBuilder builder = v; + + FieldOwnerContext backingFieldContext = context; + List additionalVisibleAnnotations = Collections.emptyList(); + if (DescriptorAsmUtil.isInstancePropertyWithStaticBackingField(propertyDescriptor) ) { + modifiers |= ACC_STATIC; + + if (DescriptorsJvmAbiUtil.isPropertyWithBackingFieldInOuterClass(propertyDescriptor)) { + ImplementationBodyCodegen codegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); + builder = codegen.v; + backingFieldContext = codegen.context; + if (DescriptorVisibilities.isPrivate(((ClassDescriptor) propertyDescriptor.getContainingDeclaration()).getVisibility())) { + modifiers |= ACC_DEPRECATED; + additionalVisibleAnnotations = Collections.singletonList(CodegenUtilKt.JAVA_LANG_DEPRECATED); + } + } + } + modifiers |= getVisibilityForBackingField(propertyDescriptor, isDelegate); + + if (DescriptorAsmUtil.isPropertyWithBackingFieldCopyInOuterClass(propertyDescriptor)) { + ImplementationBodyCodegen parentBodyCodegen = (ImplementationBodyCodegen) memberCodegen.getParentCodegen(); + parentBodyCodegen.addCompanionObjectPropertyToCopy(propertyDescriptor, defaultValue); + } + + String name = backingFieldContext.getFieldName(propertyDescriptor, isDelegate); + + v.getSerializationBindings().put(FIELD_FOR_PROPERTY, propertyDescriptor, new Pair<>(type, name)); + + if (isBackingFieldOwner) { + String signature = isDelegate ? null : typeMapper.mapFieldSignature(kotlinType, propertyDescriptor); + FieldVisitor fv = builder.newField( + JvmDeclarationOriginKt.OtherOrigin(propertyDescriptor), modifiers, name, type.getDescriptor(), + signature, defaultValue + ); + + if (annotatedField != null) { + // Don't emit nullability annotations for backing field if: + // - backing field is synthetic; + // - property is lateinit (since corresponding field is actually nullable). + boolean skipNullabilityAnnotations = + (modifiers & ACC_SYNTHETIC) != 0 || + propertyDescriptor.isLateInit(); + AnnotationCodegen.forField(fv, memberCodegen, state, skipNullabilityAnnotations) + .genAnnotations(annotatedField, type, propertyDescriptor.getType(), null, additionalVisibleAnnotations); + } + + if (propertyDescriptor.getContainingDeclaration() instanceof ClassDescriptor && JvmAnnotationUtilKt.isJvmRecord((ClassDescriptor) propertyDescriptor.getContainingDeclaration())) { + // builder.newRecordComponent(name, type.getDescriptor(), signature); + } + } + } + + @NotNull + public static KotlinType getDelegateTypeForProperty( + @NotNull PropertyDescriptor propertyDescriptor, + @NotNull BindingContext bindingContext + ) { + ResolvedCall provideDelegateResolvedCall = + bindingContext.get(BindingContext.PROVIDE_DELEGATE_RESOLVED_CALL, propertyDescriptor); + + KtProperty property = (KtProperty) DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor); + KtExpression delegateExpression = property != null ? property.getDelegateExpression() : null; + + KotlinType delegateType; + if (provideDelegateResolvedCall != null) { + delegateType = provideDelegateResolvedCall.getResultingDescriptor().getReturnType(); + } + else if (delegateExpression != null) { + delegateType = bindingContext.getType(delegateExpression); + } + else { + delegateType = null; + } + + if (delegateType == null) { + // Delegation convention is unresolved + delegateType = ErrorUtils.createErrorType("Delegate type"); + } + return delegateType; + } + + private boolean shouldWriteFieldInitializer(@NotNull PropertyDescriptor descriptor) { + if (!descriptor.isConst() && + state.getLanguageVersionSettings().supportsFeature(LanguageFeature.NoConstantValueAttributeForNonConstVals)) { + return false; + } + + //final field of primitive or String type + if (!descriptor.isVar()) { + Type type = typeMapper.mapType(descriptor); + return AsmUtil.isPrimitive(type) || "java.lang.String".equals(type.getClassName()); + } + return false; + } + + private void generateGetter(@NotNull PropertyDescriptor descriptor, @Nullable KtPropertyAccessor getter) { + generateAccessor( + getter, + descriptor.getGetter() != null ? descriptor.getGetter() : DescriptorFactory.createDefaultGetter( + descriptor, Annotations.Companion.getEMPTY() + ) + ); + } + + private void generateSetter(@NotNull PropertyDescriptor descriptor, @Nullable KtPropertyAccessor setter) { + if (!descriptor.isVar()) return; + + generateAccessor( + setter, + descriptor.getSetter() != null ? descriptor.getSetter() : DescriptorFactory.createDefaultSetter( + descriptor, Annotations.Companion.getEMPTY(), Annotations.Companion.getEMPTY() + ) + ); + } + + private void generateAccessor(@Nullable KtPropertyAccessor accessor, @NotNull PropertyAccessorDescriptor descriptor) { + if (context instanceof MultifileClassFacadeContext && + (DescriptorVisibilities.isPrivate(descriptor.getVisibility()) || + DescriptorAsmUtil.getVisibilityAccessFlag(descriptor) == Opcodes.ACC_PRIVATE)) { + return; + } + + FunctionGenerationStrategy strategy; + if (accessor == null || !accessor.hasBody()) { + if (descriptor.getCorrespondingProperty().isDelegated()) { + strategy = new DelegatedPropertyAccessorStrategy(state, descriptor); + } + else { + strategy = new DefaultPropertyAccessorStrategy(state, descriptor); + } + } + else { + strategy = new FunctionGenerationStrategy.FunctionDefault(state, accessor); + } + + functionCodegen.generateMethod(JvmDeclarationOriginKt.OtherOrigin(descriptor), descriptor, strategy); + } + + private static class DefaultPropertyAccessorStrategy extends FunctionGenerationStrategy.CodegenBased { + private final PropertyAccessorDescriptor propertyAccessorDescriptor; + + public DefaultPropertyAccessorStrategy(@NotNull GenerationState state, @NotNull PropertyAccessorDescriptor descriptor) { + super(state); + propertyAccessorDescriptor = descriptor; + } + + @Override + public void doGenerateBody(@NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature) { + InstructionAdapter v = codegen.v; + PropertyDescriptor propertyDescriptor = propertyAccessorDescriptor.getCorrespondingProperty(); + StackValue property = codegen.intermediateValueForProperty(propertyDescriptor, true, null, StackValue.LOCAL_0); + + PsiElement jetProperty = DescriptorToSourceUtils.descriptorToDeclaration(propertyDescriptor); + if (jetProperty instanceof KtProperty || jetProperty instanceof KtParameter) { + codegen.markLineNumber((KtElement) jetProperty, false); + } + + if (propertyAccessorDescriptor instanceof PropertyGetterDescriptor) { + Type type = signature.getReturnType(); + property.put(type, v); + v.areturn(type); + } + else if (propertyAccessorDescriptor instanceof PropertySetterDescriptor) { + List valueParameters = propertyAccessorDescriptor.getValueParameters(); + assert valueParameters.size() == 1 : "Property setter should have only one value parameter but has " + propertyAccessorDescriptor; + int parameterIndex = codegen.lookupLocalIndex(valueParameters.get(0)); + assert parameterIndex >= 0 : "Local index for setter parameter should be positive or zero: " + propertyAccessorDescriptor; + Type type = codegen.typeMapper.mapType(propertyDescriptor); + property.store(StackValue.local(parameterIndex, type), codegen.v); + v.visitInsn(RETURN); + } + else { + throw new IllegalStateException("Unknown property accessor: " + propertyAccessorDescriptor); + } + } + } + + public static StackValue invokeDelegatedPropertyConventionMethod( + @NotNull ExpressionCodegen codegen, + @NotNull ResolvedCall resolvedCall, + @NotNull StackValue receiver, + @NotNull PropertyDescriptor propertyDescriptor + ) { + codegen.tempVariables.put( + resolvedCall.getCall().getValueArguments().get(1).asElement(), + getDelegatedPropertyMetadata(propertyDescriptor, codegen.getBindingContext()) + ); + + return codegen.invokeFunction(resolvedCall, receiver); + } + + public static boolean isDelegatedPropertyWithOptimizedMetadata( + @NotNull VariableDescriptorWithAccessors descriptor, + @NotNull BindingContext bindingContext + ) { + return Boolean.TRUE == bindingContext.get(DELEGATED_PROPERTY_WITH_OPTIMIZED_METADATA, descriptor); + } + + public static @NotNull StackValue getOptimizedDelegatedPropertyMetadataValue() { + return StackValue.constant(null, K_PROPERTY_TYPE); + } + + @NotNull + public static StackValue getDelegatedPropertyMetadata( + @NotNull VariableDescriptorWithAccessors descriptor, + @NotNull BindingContext bindingContext + ) { + if (isDelegatedPropertyWithOptimizedMetadata(descriptor, bindingContext)) { + return getOptimizedDelegatedPropertyMetadataValue(); + } + + Type owner = bindingContext.get(DELEGATED_PROPERTY_METADATA_OWNER, descriptor); + assert owner != null : "Delegated property owner not found: " + descriptor; + + List allDelegatedProperties = bindingContext.get(DELEGATED_PROPERTIES_WITH_METADATA, owner); + int index = allDelegatedProperties == null ? -1 : allDelegatedProperties.indexOf(descriptor); + if (index < 0) { + throw new AssertionError("Delegated property not found in " + owner + ": " + descriptor); + } + + StackValue.Field array = StackValue.field( + Type.getType("[" + K_PROPERTY_TYPE), owner, JvmAbi.DELEGATED_PROPERTIES_ARRAY_NAME, true, StackValue.none() + ); + return StackValue.arrayElement(K_PROPERTY_TYPE, null, array, StackValue.constant(index)); + } + + private static class DelegatedPropertyAccessorStrategy extends FunctionGenerationStrategy.CodegenBased { + private final PropertyAccessorDescriptor propertyAccessorDescriptor; + + public DelegatedPropertyAccessorStrategy(@NotNull GenerationState state, @NotNull PropertyAccessorDescriptor descriptor) { + super(state); + this.propertyAccessorDescriptor = descriptor; + } + + @Override + public void doGenerateBody(@NotNull ExpressionCodegen codegen, @NotNull JvmMethodSignature signature) { + InstructionAdapter v = codegen.v; + + BindingContext bindingContext = state.getBindingContext(); + ResolvedCall resolvedCall = + bindingContext.get(BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, propertyAccessorDescriptor); + assert resolvedCall != null : "Resolve call should be recorded for delegate call " + signature.toString(); + + PropertyDescriptor propertyDescriptor = propertyAccessorDescriptor.getCorrespondingProperty(); + StackValue.Property property = codegen.intermediateValueForProperty(propertyDescriptor, true, null, StackValue.LOCAL_0); + StackValue.Property delegate = property.getDelegateOrNull(); + assert delegate != null : "No delegate for delegated property: " + propertyDescriptor; + StackValue lastValue = invokeDelegatedPropertyConventionMethod(codegen, resolvedCall, delegate, propertyDescriptor); + Type asmType = signature.getReturnType(); + KotlinType kotlinReturnType = propertyAccessorDescriptor.getOriginal().getReturnType(); + lastValue.put(asmType, kotlinReturnType, v); + v.areturn(asmType); + } + } + + public void genDelegate(@NotNull PropertyDescriptor delegate, @NotNull PropertyDescriptor delegateTo, @NotNull StackValue field) { + ClassDescriptor toClass = (ClassDescriptor) delegateTo.getContainingDeclaration(); + + PropertyGetterDescriptor getter = delegate.getGetter(); + if (getter != null) { + //noinspection ConstantConditions + functionCodegen.genDelegate(getter, delegateTo.getGetter().getOriginal(), toClass, field); + } + + PropertySetterDescriptor setter = delegate.getSetter(); + if (setter != null) { + //noinspection ConstantConditions + functionCodegen.genDelegate(setter, delegateTo.getSetter().getOriginal(), toClass, field); + } + } +} diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.201 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.201 new file mode 100644 index 00000000000..6120985d6ce --- /dev/null +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.201 @@ -0,0 +1,703 @@ +/* + * Copyright 2010-2017 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.cli.jvm.compiler + +import com.intellij.codeInsight.ExternalAnnotationsManager +import com.intellij.codeInsight.InferredAnnotationsManager +import com.intellij.core.CoreApplicationEnvironment +import com.intellij.core.CoreJavaFileManager +import com.intellij.core.JavaCoreProjectEnvironment +import com.intellij.ide.highlighter.JavaFileType +import com.intellij.lang.java.JavaParserDefinition +import com.intellij.mock.MockProject +import com.intellij.openapi.Disposable +import com.intellij.openapi.application.TransactionGuard +import com.intellij.openapi.application.TransactionGuardImpl +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.extensions.Extensions +import com.intellij.openapi.extensions.ExtensionsArea +import com.intellij.openapi.fileTypes.PlainTextFileType +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.LanguageLevelProjectExtension +import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.io.FileUtilRt +import com.intellij.openapi.util.text.StringUtil +import com.intellij.openapi.vfs.* +import com.intellij.openapi.vfs.impl.ZipHandler +import com.intellij.pom.java.LanguageLevel +import com.intellij.psi.PsiElementFinder +import com.intellij.psi.PsiManager +import com.intellij.psi.compiled.ClassFileDecompilers +import com.intellij.psi.impl.JavaClassSupersImpl +import com.intellij.psi.impl.PsiElementFinderImpl +import com.intellij.psi.impl.PsiTreeChangePreprocessor +import com.intellij.psi.impl.file.impl.JavaFileManager +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.JavaClassSupers +import com.intellij.util.io.URLUtil +import com.intellij.util.lang.UrlClassLoader +import org.jetbrains.annotations.TestOnly +import org.jetbrains.kotlin.asJava.KotlinAsJavaSupport +import org.jetbrains.kotlin.asJava.LightClassGenerationSupport +import org.jetbrains.kotlin.asJava.classes.FacadeCache +import org.jetbrains.kotlin.asJava.finder.JavaElementFinder +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.CliModuleVisibilityManagerImpl +import org.jetbrains.kotlin.cli.common.KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY +import org.jetbrains.kotlin.cli.common.config.ContentRoot +import org.jetbrains.kotlin.cli.common.config.KotlinSourceRoot +import org.jetbrains.kotlin.cli.common.config.kotlinSourceRoots +import org.jetbrains.kotlin.cli.common.extensions.ScriptEvaluationExtension +import org.jetbrains.kotlin.cli.common.extensions.ShellExtension +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.STRONG_WARNING +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.common.toBooleanLenient +import org.jetbrains.kotlin.cli.jvm.JvmRuntimeVersionsConsistencyChecker +import org.jetbrains.kotlin.cli.jvm.config.* +import org.jetbrains.kotlin.cli.jvm.index.* +import org.jetbrains.kotlin.cli.jvm.javac.JavacWrapperRegistrar +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleFinder +import org.jetbrains.kotlin.cli.jvm.modules.CliJavaModuleResolver +import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension +import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar +import org.jetbrains.kotlin.config.* +import org.jetbrains.kotlin.extensions.* +import org.jetbrains.kotlin.extensions.internal.CandidateInterceptor +import org.jetbrains.kotlin.extensions.internal.TypeResolutionInterceptor +import org.jetbrains.kotlin.idea.KotlinFileType +import org.jetbrains.kotlin.js.translate.extensions.JsSyntheticTranslateExtension +import org.jetbrains.kotlin.load.kotlin.KotlinBinaryClassCache +import org.jetbrains.kotlin.load.kotlin.MetadataFinderFactory +import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager +import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory +import org.jetbrains.kotlin.parsing.KotlinParserDefinition +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.CodeAnalyzerInitializer +import org.jetbrains.kotlin.resolve.ModuleAnnotationsResolver +import org.jetbrains.kotlin.resolve.extensions.ExtraImportsProviderExtension +import org.jetbrains.kotlin.resolve.extensions.SyntheticResolveExtension +import org.jetbrains.kotlin.resolve.jvm.KotlinJavaPsiFacade +import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension +import org.jetbrains.kotlin.resolve.jvm.extensions.PackageFragmentProviderExtension +import org.jetbrains.kotlin.resolve.jvm.modules.JavaModuleResolver +import org.jetbrains.kotlin.resolve.lazy.declarations.CliDeclarationProviderFactoryService +import org.jetbrains.kotlin.resolve.lazy.declarations.DeclarationProviderFactoryService +import org.jetbrains.kotlin.serialization.DescriptorSerializerPlugin +import org.jetbrains.kotlin.utils.PathUtil +import java.io.File +import java.nio.file.FileSystems +import java.util.zip.ZipFile + +class KotlinCoreEnvironment private constructor( + val projectEnvironment: JavaCoreProjectEnvironment, + private val initialConfiguration: CompilerConfiguration, + configFiles: EnvironmentConfigFiles +) { + + class ProjectEnvironment( + disposable: Disposable, + applicationEnvironment: KotlinCoreApplicationEnvironment + ) : + KotlinCoreProjectEnvironment(disposable, applicationEnvironment) { + + private var extensionRegistered = false + + override fun preregisterServices() { + registerProjectExtensionPoints(project.extensionArea) + } + + fun registerExtensionsFromPlugins(configuration: CompilerConfiguration) { + if (!extensionRegistered) { + registerPluginExtensionPoints(project) + registerExtensionsFromPlugins(project, configuration) + extensionRegistered = true + } + } + + override fun registerJavaPsiFacade() { + with(project) { + registerService( + CoreJavaFileManager::class.java, + ServiceManager.getService(this, JavaFileManager::class.java) as CoreJavaFileManager + ) + + registerKotlinLightClassSupport(project) + + registerService(ExternalAnnotationsManager::class.java, MockExternalAnnotationsManager()) + registerService(InferredAnnotationsManager::class.java, MockInferredAnnotationsManager()) + } + + super.registerJavaPsiFacade() + } + } + + private val sourceFiles = mutableListOf() + private val rootsIndex: JvmDependenciesDynamicCompoundIndex + private val packagePartProviders = mutableListOf() + + private val classpathRootsResolver: ClasspathRootsResolver + private val initialRoots = ArrayList() + + val configuration: CompilerConfiguration = initialConfiguration.apply { setupJdkClasspathRoots(configFiles) }.copy() + + init { + PersistentFSConstants::class.java.getDeclaredField("ourMaxIntellisenseFileSize") + .apply { isAccessible = true } + .setInt(null, FileUtilRt.LARGE_FOR_CONTENT_LOADING) + + val project = projectEnvironment.project + + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + + (projectEnvironment as? ProjectEnvironment)?.registerExtensionsFromPlugins(configuration) + // otherwise consider that project environment is properly configured before passing to the environment + // TODO: consider some asserts to check important extension points + + project.registerService(DeclarationProviderFactoryService::class.java, CliDeclarationProviderFactoryService(sourceFiles)) + + val isJvm = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES + project.registerService(ModuleVisibilityManager::class.java, CliModuleVisibilityManagerImpl(isJvm)) + + registerProjectServicesForCLI(projectEnvironment) + + registerProjectServices(projectEnvironment.project) + + for (extension in CompilerConfigurationExtension.getInstances(project)) { + extension.updateConfiguration(configuration) + } + + sourceFiles += createKtFiles(project) + + collectAdditionalSources(project) + + sourceFiles.sortBy { it.virtualFile.path } + + val jdkHome = configuration.get(JVMConfigurationKeys.JDK_HOME) + val jrtFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JRT_PROTOCOL) + val javaModuleFinder = CliJavaModuleFinder(jdkHome?.path?.let { path -> + jrtFileSystem?.findFileByPath(path + URLUtil.JAR_SEPARATOR) + }) + + val outputDirectory = + configuration.get(JVMConfigurationKeys.MODULES)?.singleOrNull()?.getOutputDirectory() + ?: configuration.get(JVMConfigurationKeys.OUTPUT_DIRECTORY)?.absolutePath + + classpathRootsResolver = ClasspathRootsResolver( + PsiManager.getInstance(project), + messageCollector, + configuration.getList(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES), + this::contentRootToVirtualFile, + javaModuleFinder, + !configuration.getBoolean(CLIConfigurationKeys.ALLOW_KOTLIN_PACKAGE), + outputDirectory?.let(this::findLocalFile) + ) + + val (initialRoots, javaModules) = + classpathRootsResolver.convertClasspathRoots(configuration.getList(CLIConfigurationKeys.CONTENT_ROOTS)) + this.initialRoots.addAll(initialRoots) + + if (!configuration.getBoolean(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK) && messageCollector != null) { + JvmRuntimeVersionsConsistencyChecker.checkCompilerClasspathConsistency( + messageCollector, + configuration, + initialRoots.mapNotNull { (file, type) -> if (type == JavaRoot.RootType.BINARY) file else null } + ) + } + + val (roots, singleJavaFileRoots) = + initialRoots.partition { (file) -> file.isDirectory || file.extension != JavaFileType.DEFAULT_EXTENSION } + + // REPL and kapt2 update classpath dynamically + rootsIndex = JvmDependenciesDynamicCompoundIndex().apply { + addIndex(JvmDependenciesIndexImpl(roots)) + updateClasspathFromRootsIndex(this) + } + + (ServiceManager.getService(project, CoreJavaFileManager::class.java) as KotlinCliJavaFileManagerImpl).initialize( + rootsIndex, + packagePartProviders, + SingleJavaFileRootsIndex(singleJavaFileRoots), + configuration.getBoolean(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING) + ) + + project.registerService( + JavaModuleResolver::class.java, + CliJavaModuleResolver(classpathRootsResolver.javaModuleGraph, javaModules, javaModuleFinder.systemModules.toList()) + ) + + val finderFactory = CliVirtualFileFinderFactory(rootsIndex) + project.registerService(MetadataFinderFactory::class.java, finderFactory) + project.registerService(VirtualFileFinderFactory::class.java, finderFactory) + + project.putUserData(APPEND_JAVA_SOURCE_ROOTS_HANDLER_KEY, fun(roots: List) { + updateClasspath(roots.map { JavaSourceRoot(it, null) }) + }) + } + + private fun collectAdditionalSources(project: MockProject) { + var unprocessedSources: Collection = sourceFiles + val processedSources = HashSet() + val processedSourcesByExtension = HashMap>() + // repeat feeding extensions with sources while new sources a being added + var sourceCollectionIterations = 0 + while (unprocessedSources.isNotEmpty()) { + if (sourceCollectionIterations++ > 10) { // TODO: consider using some appropriate global constant + throw IllegalStateException("Unable to collect additional sources in reasonable number of iterations") + } + processedSources.addAll(unprocessedSources) + val allNewSources = ArrayList() + for (extension in CollectAdditionalSourcesExtension.getInstances(project)) { + // do not feed the extension with the sources it returned on the previous iteration + val sourcesToProcess = unprocessedSources - (processedSourcesByExtension[extension] ?: emptyList()) + val newSources = extension.collectAdditionalSourcesAndUpdateConfiguration(sourcesToProcess, configuration, project) + if (newSources.isNotEmpty()) { + allNewSources.addAll(newSources) + processedSourcesByExtension[extension] = newSources + } + } + unprocessedSources = allNewSources.filterNot { processedSources.contains(it) }.distinct() + sourceFiles += unprocessedSources + } + } + + fun addKotlinSourceRoots(rootDirs: List) { + val roots = rootDirs.map { KotlinSourceRoot(it.absolutePath, isCommon = false) } + sourceFiles += createSourceFilesFromSourceRoots(configuration, project, roots) + } + + fun createPackagePartProvider(scope: GlobalSearchScope): JvmPackagePartProvider { + return JvmPackagePartProvider(configuration.languageVersionSettings, scope).apply { + addRoots(initialRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)) + packagePartProviders += this + (ModuleAnnotationsResolver.getInstance(project) as CliModuleAnnotationsResolver).addPackagePartProvider(this) + } + } + + private val VirtualFile.javaFiles: List + get() = mutableListOf().apply { + VfsUtilCore.processFilesRecursively(this@javaFiles) { file -> + if (file.fileType == JavaFileType.INSTANCE) { + add(file) + } + true + } + } + + private val allJavaFiles: List + get() = configuration.javaSourceRoots + .mapNotNull(this::findLocalFile) + .flatMap { it.javaFiles } + .map { File(it.canonicalPath) } + + fun registerJavac( + javaFiles: List = allJavaFiles, + kotlinFiles: List = sourceFiles, + arguments: Array? = null, + bootClasspath: List? = null, + sourcePath: List? = null + ): Boolean { + return JavacWrapperRegistrar.registerJavac( + projectEnvironment.project, configuration, javaFiles, kotlinFiles, arguments, bootClasspath, sourcePath, + LightClassGenerationSupport.getInstance(project), packagePartProviders + ) + } + + private val applicationEnvironment: CoreApplicationEnvironment + get() = projectEnvironment.environment + + val project: Project + get() = projectEnvironment.project + + internal fun countLinesOfCode(sourceFiles: List): Int = + sourceFiles.sumBy { sourceFile -> + val text = sourceFile.text + StringUtil.getLineBreakCount(text) + (if (StringUtil.endsWithLineBreak(text)) 0 else 1) + } + + private fun updateClasspathFromRootsIndex(index: JvmDependenciesIndex) { + index.indexedRoots.forEach { + projectEnvironment.addSourcesToClasspath(it.file) + } + } + + fun updateClasspath(contentRoots: List): List? { + // TODO: add new Java modules to CliJavaModuleResolver + val newRoots = classpathRootsResolver.convertClasspathRoots(contentRoots).roots + + if (packagePartProviders.isEmpty()) { + initialRoots.addAll(newRoots) + } else { + for (packagePartProvider in packagePartProviders) { + packagePartProvider.addRoots(newRoots, configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)) + } + } + + return rootsIndex.addNewIndexForRoots(newRoots)?.let { newIndex -> + updateClasspathFromRootsIndex(newIndex) + newIndex.indexedRoots.mapNotNull { (file) -> + VfsUtilCore.virtualToIoFile(VfsUtilCore.getVirtualFileForJar(file) ?: file) + }.toList() + }.orEmpty() + } + + private fun contentRootToVirtualFile(root: JvmContentRoot): VirtualFile? = + when (root) { + is JvmClasspathRoot -> + if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Classpath entry") + is JvmModulePathRoot -> + if (root.file.isFile) findJarRoot(root.file) else findExistingRoot(root, "Java module root") + is JavaSourceRoot -> + findExistingRoot(root, "Java source root") + else -> + throw IllegalStateException("Unexpected root: $root") + } + + internal fun findLocalFile(path: String): VirtualFile? = + applicationEnvironment.localFileSystem.findFileByPath(path) + + private fun findExistingRoot(root: JvmContentRoot, rootDescription: String): VirtualFile? { + return findLocalFile(root.file.absolutePath).also { + if (it == null) { + report(STRONG_WARNING, "$rootDescription points to a non-existent location: ${root.file}") + } + } + } + + private fun findJarRoot(file: File): VirtualFile? = + applicationEnvironment.jarFileSystem.findFileByPath("$file${URLUtil.JAR_SEPARATOR}") + + private fun getSourceRootsCheckingForDuplicates(): List { + val uniqueSourceRoots = hashSetOf() + val result = mutableListOf() + + for (root in configuration.kotlinSourceRoots) { + if (!uniqueSourceRoots.add(root.path)) { + report(STRONG_WARNING, "Duplicate source root: ${root.path}") + } + result.add(root) + } + + return result + } + + fun getSourceFiles(): List = sourceFiles + + private fun createKtFiles(project: Project): List = + createSourceFilesFromSourceRoots(configuration, project, getSourceRootsCheckingForDuplicates()) + + internal fun report(severity: CompilerMessageSeverity, message: String) = configuration.report(severity, message) + + companion object { + private val LOG = Logger.getInstance(KotlinCoreEnvironment::class.java) + + private val APPLICATION_LOCK = Object() + private var ourApplicationEnvironment: KotlinCoreApplicationEnvironment? = null + private var ourProjectCount = 0 + + @JvmStatic + fun createForProduction( + parentDisposable: Disposable, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + setupIdeaStandaloneExecution() + val appEnv = getOrCreateApplicationEnvironmentForProduction(parentDisposable, configuration) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + val environment = KotlinCoreEnvironment(projectEnv, configuration, configFiles) + + synchronized(APPLICATION_LOCK) { + ourProjectCount++ + } + return environment + } + + @JvmStatic + fun createForProduction( + projectEnvironment: JavaCoreProjectEnvironment, configuration: CompilerConfiguration, configFiles: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val environment = KotlinCoreEnvironment(projectEnvironment, configuration, configFiles) + + if (projectEnvironment.environment == applicationEnvironment) { + // accounting for core environment disposing + synchronized(APPLICATION_LOCK) { + ourProjectCount++ + } + } + return environment + } + + @TestOnly + @JvmStatic + fun createForTests( + parentDisposable: Disposable, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + val configuration = initialConfiguration.copy() + // Tests are supposed to create a single project and dispose it right after use + val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + val projectEnv = ProjectEnvironment(parentDisposable, appEnv) + return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) + } + + // used in the daemon for jar cache cleanup + val applicationEnvironment: KotlinCoreApplicationEnvironment? get() = ourApplicationEnvironment + + fun getOrCreateApplicationEnvironmentForProduction( + parentDisposable: Disposable, configuration: CompilerConfiguration + ): KotlinCoreApplicationEnvironment { + synchronized(APPLICATION_LOCK) { + if (ourApplicationEnvironment == null) { + val disposable = Disposer.newDisposable() + ourApplicationEnvironment = createApplicationEnvironment(disposable, configuration, unitTestMode = false) + ourProjectCount = 0 + Disposer.register(disposable, Disposable { + synchronized(APPLICATION_LOCK) { + ourApplicationEnvironment = null + } + }) + } + // Disposing of the environment is unsafe in production then parallel builds are enabled, but turning it off universally + // breaks a lot of tests, therefore it is disabled for production and enabled for tests + if (System.getProperty(KOTLIN_COMPILER_ENVIRONMENT_KEEPALIVE_PROPERTY).toBooleanLenient() != true) { + // JPS may run many instances of the compiler in parallel (there's an option for compiling independent modules in parallel in IntelliJ) + // All projects share the same ApplicationEnvironment, and when the last project is disposed, the ApplicationEnvironment is disposed as well + @Suppress("ObjectLiteralToLambda") // Disposer tree depends on identity of disposables. + Disposer.register(parentDisposable, object : Disposable { + override fun dispose() { + synchronized(APPLICATION_LOCK) { + if (--ourProjectCount <= 0) { + disposeApplicationEnvironment() + } + } + } + }) + } + + return ourApplicationEnvironment!! + } + } + + /** + * This method is also used in Gradle after configuration phase finished. + */ + fun disposeApplicationEnvironment() { + synchronized(APPLICATION_LOCK) { + val environment = ourApplicationEnvironment ?: return + ourApplicationEnvironment = null + Disposer.dispose(environment.parentDisposable) + ZipHandler.clearFileAccessorCache() + } + } + + private fun createApplicationEnvironment( + parentDisposable: Disposable, configuration: CompilerConfiguration, unitTestMode: Boolean + ): KotlinCoreApplicationEnvironment { + val applicationEnvironment = KotlinCoreApplicationEnvironment.create(parentDisposable, unitTestMode) + + registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/compiler.xml") + // FIX ME WHEN BUNCH 202 REMOVED: this code is required to support compiler bundled to both 202 and 203. + // Please, remove "com.intellij.psi.classFileDecompiler" EP registration once 202 is no longer supported by the compiler + if (!Extensions.getRootArea().hasExtensionPoint("com.intellij.psi.classFileDecompiler")) { + registerApplicationExtensionPointsAndExtensionsFrom(configuration, "extensions/core.xml") + } + + registerApplicationServicesForCLI(applicationEnvironment) + registerApplicationServices(applicationEnvironment) + + return applicationEnvironment + } + + private fun registerApplicationExtensionPointsAndExtensionsFrom(configuration: CompilerConfiguration, configFilePath: String) { + fun File.hasConfigFile(configFile: String): Boolean = + if (isDirectory) File(this, "META-INF" + File.separator + configFile).exists() + else try { + ZipFile(this).use { + it.getEntry("META-INF/$configFile") != null + } + } catch (e: Throwable) { + false + } + + val pluginRoot: File = + configuration.get(CLIConfigurationKeys.INTELLIJ_PLUGIN_ROOT)?.let(::File) + ?: PathUtil.getResourcePathForClass(this::class.java).takeIf { it.hasConfigFile(configFilePath) } + // hack for load extensions when compiler run directly from project directory (e.g. in tests) + ?: File("compiler/cli/cli-common/resources").takeIf { it.hasConfigFile(configFilePath) } + ?: throw IllegalStateException( + "Unable to find extension point configuration $configFilePath " + + "(cp:\n ${(Thread.currentThread().contextClassLoader as? UrlClassLoader)?.urls?.joinToString("\n ") { it.file }})" + ) + + CoreApplicationEnvironment.registerExtensionPointAndExtensions( + FileSystems.getDefault().getPath(pluginRoot.path), + configFilePath, + Extensions.getRootArea() + ) + } + + @JvmStatic + @Suppress("MemberVisibilityCanPrivate") // made public for CLI Android Lint + fun registerPluginExtensionPoints(project: MockProject) { + ExpressionCodegenExtension.registerExtensionPoint(project) + SyntheticResolveExtension.registerExtensionPoint(project) + ClassBuilderInterceptorExtension.registerExtensionPoint(project) + AnalysisHandlerExtension.registerExtensionPoint(project) + PackageFragmentProviderExtension.registerExtensionPoint(project) + StorageComponentContainerContributor.registerExtensionPoint(project) + DeclarationAttributeAltererExtension.registerExtensionPoint(project) + PreprocessedVirtualFileFactoryExtension.registerExtensionPoint(project) + JsSyntheticTranslateExtension.registerExtensionPoint(project) + CompilerConfigurationExtension.registerExtensionPoint(project) + CollectAdditionalSourcesExtension.registerExtensionPoint(project) + ExtraImportsProviderExtension.registerExtensionPoint(project) + IrGenerationExtension.registerExtensionPoint(project) + ScriptEvaluationExtension.registerExtensionPoint(project) + ShellExtension.registerExtensionPoint(project) + TypeResolutionInterceptor.registerExtensionPoint(project) + CandidateInterceptor.registerExtensionPoint(project) + DescriptorSerializerPlugin.registerExtensionPoint(project) + } + + internal fun registerExtensionsFromPlugins(project: MockProject, configuration: CompilerConfiguration) { + val messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + for (registrar in configuration.getList(ComponentRegistrar.PLUGIN_COMPONENT_REGISTRARS)) { + try { + registrar.registerProjectComponents(project, configuration) + } catch (e: AbstractMethodError) { + val message = "The provided plugin ${registrar.javaClass.name} is not compatible with this version of compiler" + // Since the scripting plugin is often discovered in the compiler environment, it is often taken from the incompatible + // location, and in many cases this is not a fatal error, therefore strong warning is generated instead of exception + if (registrar.javaClass.simpleName == "ScriptingCompilerConfigurationComponentRegistrar") { + messageCollector?.report(STRONG_WARNING, "Default scripting plugin is disabled: $message") + } else { + throw IllegalStateException(message, e) + } + } + } + } + + + private fun registerApplicationServicesForCLI(applicationEnvironment: KotlinCoreApplicationEnvironment) { + // ability to get text from annotations xml files + applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml") + applicationEnvironment.registerParserDefinition(JavaParserDefinition()) + } + + // made public for Upsource + @Suppress("MemberVisibilityCanBePrivate") + @JvmStatic + fun registerApplicationServices(applicationEnvironment: KotlinCoreApplicationEnvironment) { + with(applicationEnvironment) { + registerFileType(KotlinFileType.INSTANCE, "kt") + registerFileType(KotlinFileType.INSTANCE, KotlinParserDefinition.STD_SCRIPT_SUFFIX) + registerParserDefinition(KotlinParserDefinition()) + application.registerService(KotlinBinaryClassCache::class.java, KotlinBinaryClassCache()) + application.registerService(JavaClassSupers::class.java, JavaClassSupersImpl::class.java) + application.registerService(TransactionGuard::class.java, TransactionGuardImpl::class.java) + } + } + + @JvmStatic + fun registerProjectExtensionPoints(area: ExtensionsArea) { + CoreApplicationEnvironment.registerExtensionPoint( + area, PsiTreeChangePreprocessor.EP.name, PsiTreeChangePreprocessor::class.java + ) + CoreApplicationEnvironment.registerExtensionPoint(area, PsiElementFinder.EP.name, PsiElementFinder::class.java) + + IdeaExtensionPoints.registerVersionSpecificProjectExtensionPoints(area) + } + + // made public for Upsource + @JvmStatic + @Deprecated("Use registerProjectServices(project) instead.", ReplaceWith("registerProjectServices(projectEnvironment.project)")) + fun registerProjectServices( + projectEnvironment: JavaCoreProjectEnvironment, + @Suppress("UNUSED_PARAMETER") messageCollector: MessageCollector? + ) { + registerProjectServices(projectEnvironment.project) + } + + // made public for Android Lint + @JvmStatic + fun registerProjectServices(project: MockProject) { + with(project) { + registerService(KotlinJavaPsiFacade::class.java, KotlinJavaPsiFacade(this)) + registerService(FacadeCache::class.java, FacadeCache(this)) + registerService(ModuleAnnotationsResolver::class.java, CliModuleAnnotationsResolver()) + } + } + + private fun registerProjectServicesForCLI(@Suppress("UNUSED_PARAMETER") projectEnvironment: JavaCoreProjectEnvironment) { + /** + * Note that Kapt may restart code analysis process, and CLI services should be aware of that. + * Use PsiManager.getModificationTracker() to ensure that all the data you cached is still valid. + */ + } + + // made public for Android Lint + @JvmStatic + fun registerKotlinLightClassSupport(project: MockProject) { + with(project) { + val traceHolder = CliTraceHolder() + val cliLightClassGenerationSupport = CliLightClassGenerationSupport(traceHolder, project) + val kotlinAsJavaSupport = CliKotlinAsJavaSupport(this, traceHolder) + registerService(LightClassGenerationSupport::class.java, cliLightClassGenerationSupport) + registerService(CliLightClassGenerationSupport::class.java, cliLightClassGenerationSupport) + registerService(KotlinAsJavaSupport::class.java, kotlinAsJavaSupport) + registerService(CodeAnalyzerInitializer::class.java, traceHolder) + + // We don't pass Disposable because in some tests, we manually unregister these extensions, and that leads to LOG.error + // exception from `ExtensionPointImpl.doRegisterExtension`, because the registered extension can no longer be found + // when the project is being disposed. + // For example, see the `unregisterExtension` call in `GenerationUtils.compileFilesUsingFrontendIR`. + // TODO: refactor this to avoid registering unneeded extensions in the first place, and avoid using deprecated API. + @Suppress("DEPRECATION") + PsiElementFinder.EP.getPoint(project).registerExtension(JavaElementFinder(this, kotlinAsJavaSupport)) + @Suppress("DEPRECATION") + PsiElementFinder.EP.getPoint(project).registerExtension(PsiElementFinderImpl(this)) + } + } + + private fun CompilerConfiguration.setupJdkClasspathRoots(configFiles: EnvironmentConfigFiles) { + if (getBoolean(JVMConfigurationKeys.NO_JDK)) return + + val jvmTarget = configFiles == EnvironmentConfigFiles.JVM_CONFIG_FILES + if (!jvmTarget) return + + val jdkHome = get(JVMConfigurationKeys.JDK_HOME) + val (javaRoot, classesRoots) = if (jdkHome == null) { + val javaHome = File(System.getProperty("java.home")) + put(JVMConfigurationKeys.JDK_HOME, javaHome) + + javaHome to PathUtil.getJdkClassesRootsFromCurrentJre() + } else { + jdkHome to PathUtil.getJdkClassesRoots(jdkHome) + } + + if (!CoreJrtFileSystem.isModularJdk(javaRoot)) { + if (classesRoots.isEmpty()) { + report(ERROR, "No class roots are found in the JDK path: $javaRoot") + } else { + addJvmSdkRoots(classesRoots) + } + } + } + } +} diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 new file mode 100644 index 00000000000..f27ad043172 --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ClassCodegen.kt.201 @@ -0,0 +1,496 @@ +/* + * 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.codegen + +import org.jetbrains.kotlin.backend.jvm.JvmBackendContext +import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin +import org.jetbrains.kotlin.backend.jvm.lower.MultifileFacadeFileEntry +import org.jetbrains.kotlin.backend.jvm.lower.buildAssertionsDisabledField +import org.jetbrains.kotlin.backend.jvm.lower.hasAssertionsDisabledField +import org.jetbrains.kotlin.codegen.DescriptorAsmUtil +import org.jetbrains.kotlin.codegen.inline.* +import org.jetbrains.kotlin.codegen.writeKotlinMetadata +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.DescriptorVisibilities +import org.jetbrains.kotlin.descriptors.DescriptorVisibility +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.ir.builders.declarations.addFunction +import org.jetbrains.kotlin.ir.declarations.* +import org.jetbrains.kotlin.ir.descriptors.toIrBasedDescriptor +import org.jetbrains.kotlin.ir.expressions.IrBlockBody +import org.jetbrains.kotlin.ir.expressions.IrConst +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrFunctionReference +import org.jetbrains.kotlin.ir.expressions.impl.IrBlockBodyImpl +import org.jetbrains.kotlin.ir.expressions.impl.IrSetFieldImpl +import org.jetbrains.kotlin.ir.util.* +import org.jetbrains.kotlin.load.java.JvmAnnotationNames +import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader +import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.protobuf.MessageLite +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_RECORD_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.JVM_SYNTHETIC_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.TRANSIENT_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.annotations.VOLATILE_ANNOTATION_FQ_NAME +import org.jetbrains.kotlin.resolve.jvm.checkers.JvmSimpleNameBacktickChecker +import org.jetbrains.kotlin.resolve.jvm.diagnostics.* +import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature +import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.commons.Method +import org.jetbrains.org.objectweb.asm.tree.MethodNode +import java.io.File + +interface MetadataSerializer { + fun serialize(metadata: MetadataSource): Pair? + fun bindMethodMetadata(metadata: MetadataSource.Property, signature: Method) + fun bindMethodMetadata(metadata: MetadataSource.Function, signature: Method) + fun bindFieldMetadata(metadata: MetadataSource.Property, signature: Pair) +} + +class ClassCodegen private constructor( + val irClass: IrClass, + val context: JvmBackendContext, + private val parentFunction: IrFunction?, +) : InnerClassConsumer { + private val parentClassCodegen = (parentFunction?.parentAsClass ?: irClass.parent as? IrClass)?.let { getOrCreate(it, context) } + private val withinInline: Boolean = parentClassCodegen?.withinInline == true || parentFunction?.isInline == true + + private val state get() = context.state + private val typeMapper get() = context.typeMapper + + val type: Type = typeMapper.mapClass(irClass) + + val reifiedTypeParametersUsages = ReifiedTypeParametersUsages() + + private val jvmSignatureClashDetector = JvmSignatureClashDetector(irClass, type, context) + + private val classOrigin = irClass.descriptorOrigin + + private val visitor = state.factory.newVisitor(classOrigin, type, irClass.fileParent.loadSourceFilesInfo()).apply { + val signature = typeMapper.mapClassSignature(irClass, type) + // Ensure that the backend only produces class names that would be valid in the frontend for JVM. + if (context.state.classBuilderMode.generateBodies && signature.hasInvalidName()) { + throw IllegalStateException("Generating class with invalid name '${type.className}': ${irClass.dump()}") + } + defineClass( + irClass.psiElement, + state.classFileVersion, + irClass.flags, + signature.name, + signature.javaGenericSignature, + signature.superclassName, + signature.interfaces.toTypedArray() + ) + } + + // TODO: the order of entries in this set depends on the order in which methods are generated; this means it is unstable + // under incremental compilation, as calls to `inline fun`s declared in this class cause them to be generated out of order. + private val innerClasses = linkedSetOf() + + // TODO: the names produced by generators in this map depend on the order in which methods are generated; see above. + private val regeneratedObjectNameGenerators = mutableMapOf() + + fun getRegeneratedObjectNameGenerator(function: IrFunction): NameGenerator { + val name = if (function.name.isSpecial) "special" else function.name.asString() + return regeneratedObjectNameGenerators.getOrPut(name) { + NameGenerator("${type.internalName}\$$name\$\$inlined") + } + } + + private var generated = false + + fun generate() { + // TODO: reject repeated generate() calls; currently, these can happen for objects in finally + // blocks since they are `accept`ed once per each CFG edge out of the try-finally. + if (generated) return + generated = true + + // We remove reads of `$$delegatedProperties` (and the field itself) if they are not in fact used for anything. + val delegatedProperties = irClass.fields.singleOrNull { it.origin == JvmLoweredDeclarationOrigin.GENERATED_PROPERTY_REFERENCE } + val delegatedPropertyOptimizer = if (delegatedProperties != null) DelegatedPropertyOptimizer() else null + // Generating a method node may cause the addition of a field with an initializer if an inline function + // call uses `assert` and the JVM assertions mode is enabled. To avoid concurrent modification errors, + // there is a very specific generation order. + val smap = context.getSourceMapper(irClass) + // 1. Any method other than `` can add a field and a `` statement: + for (method in irClass.declarations.filterIsInstance()) { + if (method.name.asString() != "") { + generateMethod(method, smap, delegatedPropertyOptimizer) + } + } + // 2. `` itself can add a field, but the statement is generated via the `return init` hack: + irClass.functions.find { it.name.asString() == "" }?.let { generateMethod(it, smap, delegatedPropertyOptimizer) } + // 3. Now we have all the fields (`$$delegatedProperties` might be redundant if all reads were optimized out): + for (field in irClass.fields) { + if (field !== delegatedProperties || delegatedPropertyOptimizer?.needsDelegatedProperties == true) { + generateField(field) + } + } + // 4. Generate nested classes at the end, to ensure that when the companion's metadata is serialized + // everything moved to the outer class has already been recorded in `globalSerializationBindings`. + for (declaration in irClass.declarations) { + if (declaration is IrClass) { + getOrCreate(declaration, context).generate() + } + } + + object : AnnotationCodegen(this@ClassCodegen, context) { + override fun visitAnnotation(descr: String?, visible: Boolean): AnnotationVisitor { + return visitor.visitor.visitAnnotation(descr, visible) + } + }.genAnnotations(irClass, null, null) + generateKotlinMetadataAnnotation() + + generateInnerAndOuterClasses() + + if (withinInline || !smap.isTrivial) { + visitor.visitSMAP(smap, !context.state.languageVersionSettings.supportsFeature(LanguageFeature.CorrectSourceMappingSyntax)) + } else { + smap.sourceInfo!!.sourceFileName?.let { + visitor.visitSource(it, null) + } + } + + visitor.done() + jvmSignatureClashDetector.reportErrors(classOrigin) + } + + fun generateAssertFieldIfNeeded(generatingClInit: Boolean): IrExpression? { + if (irClass.hasAssertionsDisabledField(context)) + return null + val topLevelClass = generateSequence(this) { it.parentClassCodegen }.last().irClass + val field = irClass.buildAssertionsDisabledField(context, topLevelClass) + generateField(field) + // Normally, `InitializersLowering` would move the initializer to , but + // it's obviously too late for that. + val init = IrSetFieldImpl( + field.startOffset, field.endOffset, field.symbol, null, + field.initializer!!.expression, context.irBuiltIns.unitType + ) + if (generatingClInit) { + // Too late to modify the IR; have to ask the currently active `ExpressionCodegen` + // to generate this statement directly. + return init + } + val classInitializer = irClass.functions.singleOrNull { it.name.asString() == "" } ?: irClass.addFunction { + name = Name.special("") + returnType = context.irBuiltIns.unitType + }.apply { + body = IrBlockBodyImpl(startOffset, endOffset) + } + (classInitializer.body as IrBlockBody).statements.add(0, init) + return null + } + + private val metadataSerializer: MetadataSerializer = + context.serializerFactory(context, irClass, type, visitor.serializationBindings, parentClassCodegen?.metadataSerializer) + + private fun generateKotlinMetadataAnnotation() { + // TODO: if `-Xmultifile-parts-inherit` is enabled, write the corresponding flag for parts and facades to [Metadata.extraInt]. + var extraFlags = JvmAnnotationNames.METADATA_JVM_IR_FLAG + if (state.isIrWithStableAbi) { + extraFlags += JvmAnnotationNames.METADATA_JVM_IR_STABLE_ABI_FLAG + } + + val facadeClassName = context.multifileFacadeForPart[irClass.attributeOwnerId] + val metadata = irClass.metadata + val entry = irClass.fileParent.fileEntry + val kind = when { + facadeClassName != null -> KotlinClassHeader.Kind.MULTIFILE_CLASS_PART + metadata is MetadataSource.Class -> KotlinClassHeader.Kind.CLASS + metadata is MetadataSource.File -> KotlinClassHeader.Kind.FILE_FACADE + metadata is MetadataSource.Function -> KotlinClassHeader.Kind.SYNTHETIC_CLASS + entry is MultifileFacadeFileEntry -> KotlinClassHeader.Kind.MULTIFILE_CLASS + else -> KotlinClassHeader.Kind.SYNTHETIC_CLASS + } + writeKotlinMetadata(visitor, state, kind, extraFlags) { + if (metadata != null) { + metadataSerializer.serialize(metadata)?.let { (proto, stringTable) -> + DescriptorAsmUtil.writeAnnotationData(it, proto, stringTable) + } + } + + if (entry is MultifileFacadeFileEntry) { + val arv = it.visitArray(JvmAnnotationNames.METADATA_DATA_FIELD_NAME) + for (partFile in entry.partFiles) { + val fileClass = partFile.declarations.singleOrNull { it.isFileClass } as IrClass? + if (fileClass != null) arv.visit(null, typeMapper.mapClass(fileClass).internalName) + } + arv.visitEnd() + } + + if (facadeClassName != null) { + it.visit(JvmAnnotationNames.METADATA_MULTIFILE_CLASS_NAME_FIELD_NAME, facadeClassName.internalName) + } + + if (irClass in context.classNameOverride) { + val isFileClass = kind == KotlinClassHeader.Kind.MULTIFILE_CLASS || + kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART || + kind == KotlinClassHeader.Kind.FILE_FACADE + assert(isFileClass) { "JvmPackageName is not supported for classes: ${irClass.render()}" } + it.visit(JvmAnnotationNames.METADATA_PACKAGE_NAME_FIELD_NAME, irClass.fqNameWhenAvailable!!.parent().asString()) + } + } + } + + private fun IrFile.loadSourceFilesInfo(): List { + val entry = fileEntry + if (entry is MultifileFacadeFileEntry) { + return entry.partFiles.flatMap { it.loadSourceFilesInfo() } + } + return listOfNotNull(context.psiSourceManager.getFileEntry(this)?.let { File(it.name) }) + } + + companion object { + fun getOrCreate( + irClass: IrClass, + context: JvmBackendContext, + // The `parentFunction` is only set for classes nested inside of functions. This is usually safe, since there is no + // way to refer to (inline) members of such a class from outside of the function unless the function in question is + // itself declared as inline. In that case, the function will be compiled before we can refer to the nested class. + // + // The one exception to this rule are anonymous objects defined as members of a class. These are nested inside of the + // class initializer, but can be referred to from anywhere within the scope of the class. That's why we have to ensure + // that all references to classes inside of have a non-null `parentFunction`. + parentFunction: IrFunction? = irClass.parent.safeAs()?.takeIf { + it.origin == JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER + }, + ): ClassCodegen = + context.classCodegens.getOrPut(irClass) { ClassCodegen(irClass, context, parentFunction) }.also { + assert(parentFunction == null || it.parentFunction == parentFunction) { + "inconsistent parent function for ${irClass.render()}:\n" + + "New: ${parentFunction!!.render()}\n" + + "Old: ${it.parentFunction?.render()}" + } + } + + private fun JvmClassSignature.hasInvalidName() = + name.splitToSequence('/').any { identifier -> identifier.any { it in JvmSimpleNameBacktickChecker.INVALID_CHARS } } + } + + private fun generateField(field: IrField) { + val fieldType = typeMapper.mapType(field) + val fieldSignature = + if (field.origin == IrDeclarationOrigin.PROPERTY_DELEGATE) null + else context.methodSignatureMapper.mapFieldSignature(field) + val fieldName = field.name.asString() + val flags = field.computeFieldFlags(context, state.languageVersionSettings) + val fv = visitor.newField( + field.descriptorOrigin, flags, fieldName, fieldType.descriptor, + fieldSignature, (field.initializer?.expression as? IrConst<*>)?.value + ) + + jvmSignatureClashDetector.trackField(field, RawSignature(fieldName, fieldType.descriptor, MemberKind.FIELD)) + + if (field.origin != JvmLoweredDeclarationOrigin.CONTINUATION_CLASS_RESULT_FIELD) { + val skipNullabilityAnnotations = + flags and (Opcodes.ACC_SYNTHETIC or Opcodes.ACC_ENUM) != 0 || + field.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_STATIC_CALLABLE_REFERENCE_INSTANCE + object : AnnotationCodegen(this@ClassCodegen, context, skipNullabilityAnnotations) { + override fun visitAnnotation(descr: String?, visible: Boolean): AnnotationVisitor { + return fv.visitAnnotation(descr, visible) + } + + override fun visitTypeAnnotation(descr: String?, path: TypePath?, visible: Boolean): AnnotationVisitor { + return fv.visitTypeAnnotation(TypeReference.newTypeReference(TypeReference.FIELD).value, path, descr, visible) + } + }.genAnnotations(field, fieldType, field.type) + } + + (field.metadata as? MetadataSource.Property)?.let { + metadataSerializer.bindFieldMetadata(it, fieldType to fieldName) + } + + if (irClass.hasAnnotation(JVM_RECORD_ANNOTATION_FQ_NAME) && !field.isStatic) { + // TODO: Write annotations to the component + // visitor.newRecordComponent(fieldName, fieldType.descriptor, fieldSignature) + } + } + + private val generatedInlineMethods = mutableMapOf() + + fun generateMethodNode(method: IrFunction): SMAPAndMethodNode { + if (!method.isInline && !method.isSuspendCapturingCrossinline()) { + // Inline methods can be used multiple times by `IrSourceCompilerForInline`, suspend methods + // are used twice (`f` and `f$$forInline`) if they capture crossinline lambdas, and everything + // else is only generated by `generateMethod` below so does not need caching. + // TODO: inline lambdas are not marked `isInline`, and are generally used once, but may be needed + // multiple times if declared in a `finally` block - should they be cached? + return FunctionCodegen(method, this).generate() + } + val (node, smap) = generatedInlineMethods.getOrPut(method) { FunctionCodegen(method, this).generate() } + val copy = with(node) { MethodNode(Opcodes.API_VERSION, access, name, desc, signature, exceptions.toTypedArray()) } + node.instructions.resetLabels() + node.accept(copy) + return SMAPAndMethodNode(copy, smap) + } + + private fun generateMethod(method: IrFunction, classSMAP: SourceMapper, delegatedPropertyOptimizer: DelegatedPropertyOptimizer?) { + if (method.isFakeOverride) { + jvmSignatureClashDetector.trackFakeOverrideMethod(method) + return + } + + val (node, smap) = generateMethodNode(method) + if (delegatedPropertyOptimizer != null) { + delegatedPropertyOptimizer.transform(node) + if (method.name.asString() == "") { + delegatedPropertyOptimizer.transformClassInitializer(node) + } + } + node.preprocessSuspendMarkers( + method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE || method.isEffectivelyInlineOnly(), + method.origin == JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE + ) + val mv = with(node) { visitor.newMethod(method.descriptorOrigin, access, name, desc, signature, exceptions.toTypedArray()) } + val smapCopier = SourceMapCopier(classSMAP, smap) + val smapCopyingVisitor = object : MethodVisitor(Opcodes.API_VERSION, mv) { + override fun visitLineNumber(line: Int, start: Label) = + super.visitLineNumber(smapCopier.mapLineNumber(line), start) + } + if (method.hasContinuation()) { + // Generate a state machine within this method. The continuation class for it should be generated + // lazily so that if tail call optimization kicks in, the unused class will not be written to the output. + val continuationClass = method.continuationClass() // null if `SuspendLambda.invokeSuspend` - `this` is continuation itself + val continuationClassCodegen = lazy { if (continuationClass != null) getOrCreate(continuationClass, context, method) else this } + node.acceptWithStateMachine(method, this, smapCopyingVisitor) { continuationClassCodegen.value.visitor } + if (continuationClass != null && (continuationClassCodegen.isInitialized() || method.isSuspendCapturingCrossinline())) { + continuationClassCodegen.value.generate() + } + } else { + node.accept(smapCopyingVisitor) + } + jvmSignatureClashDetector.trackMethod(method, RawSignature(node.name, node.desc, MemberKind.METHOD)) + + when (val metadata = method.metadata) { + is MetadataSource.Property -> { + assert(method.origin == JvmLoweredDeclarationOrigin.SYNTHETIC_METHOD_FOR_PROPERTY_OR_TYPEALIAS_ANNOTATIONS) { + "MetadataSource.Property on IrFunction should only be used for synthetic \$annotations methods: ${method.render()}" + } + metadataSerializer.bindMethodMetadata(metadata, Method(node.name, node.desc)) + } + is MetadataSource.Function -> metadataSerializer.bindMethodMetadata(metadata, Method(node.name, node.desc)) + null -> Unit + else -> error("Incorrect metadata source $metadata for:\n${method.dump()}") + } + } + + private fun generateInnerAndOuterClasses() { + // JVMS7 (4.7.6): a nested class or interface member will have InnerClasses information + // for each enclosing class and for each immediate member + parentClassCodegen?.innerClasses?.add(irClass) + for (codegen in generateSequence(this) { it.parentClassCodegen }.takeWhile { it.parentClassCodegen != null }) { + innerClasses.add(codegen.irClass) + } + + // JVMS7 (4.7.7): A class must have an EnclosingMethod attribute if and only if + // it is a local class or an anonymous class. + // + // The attribute contains the innermost class that encloses the declaration of + // the current class. If the current class is immediately enclosed by a method + // or constructor, the name and type of the function is recorded as well. + if (parentClassCodegen != null) { + // In case there's no primary constructor, it's unclear which constructor should be the enclosing one, so we select the first. + val enclosingFunction = if (irClass.attributeOwnerId in context.isEnclosedInConstructor) { + val containerClass = parentClassCodegen.irClass + containerClass.primaryConstructor + ?: containerClass.declarations.firstIsInstanceOrNull() + ?: error("Class in a non-static initializer found, but container has no constructors: ${containerClass.render()}") + } else parentFunction + if (enclosingFunction != null || irClass.isAnonymousObject) { + val method = enclosingFunction?.let(context.methodSignatureMapper::mapAsmMethod) + visitor.visitOuterClass(parentClassCodegen.type.internalName, method?.name, method?.descriptor) + } + } + + for (klass in innerClasses) { + val innerClass = typeMapper.classInternalName(klass) + val outerClass = + if (klass.attributeOwnerId in context.isEnclosedInConstructor) null + else klass.parent.safeAs()?.let(typeMapper::classInternalName) + val innerName = klass.name.takeUnless { it.isSpecial }?.asString() + visitor.visitInnerClass(innerClass, outerClass, innerName, klass.calculateInnerClassAccessFlags(context)) + } + } + + override fun addInnerClassInfoFromAnnotation(innerClass: IrClass) { + // It's necessary for proper recovering of classId by plain string JVM descriptor when loading annotations + // See FileBasedKotlinClass.convertAnnotationVisitor + generateSequence(innerClass) { it.parent as? IrDeclaration }.takeWhile { !it.isTopLevelDeclaration }.forEach { + if (it is IrClass) { + innerClasses.add(it) + } + } + } + + private val IrDeclaration.descriptorOrigin: JvmDeclarationOrigin + get() { + val psiElement = context.psiSourceManager.findPsiElement(this) + // For declarations inside lambdas, produce a descriptor which refers back to the original function. + // This is needed for plugins which check for lambdas inside of inline functions using the descriptor + // contained in JvmDeclarationOrigin. This matches the behavior of the JVM backend. + // TODO: this is really not very useful, as this does nothing for other anonymous objects. + val isLambda = irClass.origin == JvmLoweredDeclarationOrigin.LAMBDA_IMPL || + irClass.origin == JvmLoweredDeclarationOrigin.SUSPEND_LAMBDA + val descriptor = if (isLambda) + irClass.attributeOwnerId.safeAs()?.symbol?.owner?.toIrBasedDescriptor() ?: toIrBasedDescriptor() + else + toIrBasedDescriptor() + return if (origin == IrDeclarationOrigin.FILE_CLASS) + JvmDeclarationOrigin(JvmDeclarationOriginKind.PACKAGE_PART, psiElement, descriptor) + else + OtherOrigin(psiElement, descriptor) + } +} + +private val IrClass.flags: Int + get() = origin.flags or getVisibilityAccessFlagForClass() or + (if (isAnnotatedWithDeprecated) Opcodes.ACC_DEPRECATED else 0) or + (if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME)) Opcodes.ACC_SYNTHETIC else 0) or + when { + isAnnotationClass -> Opcodes.ACC_ANNOTATION or Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT + isInterface -> Opcodes.ACC_INTERFACE or Opcodes.ACC_ABSTRACT + isEnumClass -> Opcodes.ACC_ENUM or Opcodes.ACC_SUPER or modality.flags + else -> Opcodes.ACC_SUPER or modality.flags + } + +private fun IrField.computeFieldFlags(context: JvmBackendContext, languageVersionSettings: LanguageVersionSettings): Int = + origin.flags or visibility.flags or + (if (isDeprecatedCallable(context) || + correspondingPropertySymbol?.owner?.isDeprecatedCallable(context) == true + ) Opcodes.ACC_DEPRECATED else 0) or + (if (isFinal) Opcodes.ACC_FINAL else 0) or + (if (isStatic) Opcodes.ACC_STATIC else 0) or + (if (hasAnnotation(VOLATILE_ANNOTATION_FQ_NAME)) Opcodes.ACC_VOLATILE else 0) or + (if (hasAnnotation(TRANSIENT_ANNOTATION_FQ_NAME)) Opcodes.ACC_TRANSIENT else 0) or + (if (hasAnnotation(JVM_SYNTHETIC_ANNOTATION_FQ_NAME) || + isPrivateCompanionFieldInInterface(languageVersionSettings) + ) Opcodes.ACC_SYNTHETIC else 0) + +private fun IrField.isPrivateCompanionFieldInInterface(languageVersionSettings: LanguageVersionSettings): Boolean = + origin == IrDeclarationOrigin.FIELD_FOR_OBJECT_INSTANCE && + languageVersionSettings.supportsFeature(LanguageFeature.ProperVisibilityForCompanionObjectInstanceField) && + parentAsClass.isJvmInterface && + DescriptorVisibilities.isPrivate(parentAsClass.companionObject()!!.visibility) + +private val IrDeclarationOrigin.flags: Int + get() = (if (isSynthetic) Opcodes.ACC_SYNTHETIC else 0) or + (if (this == IrDeclarationOrigin.FIELD_FOR_ENUM_ENTRY) Opcodes.ACC_ENUM else 0) + +private val Modality.flags: Int + get() = when (this) { + Modality.ABSTRACT, Modality.SEALED -> Opcodes.ACC_ABSTRACT + Modality.FINAL -> Opcodes.ACC_FINAL + Modality.OPEN -> 0 + else -> throw AssertionError("Unsupported modality $this") + } + +private val DescriptorVisibility.flags: Int + get() = DescriptorAsmUtil.getVisibilityAccessFlag(this) ?: throw AssertionError("Unsupported visibility $this") diff --git a/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.201 b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.201 new file mode 100644 index 00000000000..345cf8ac9c7 --- /dev/null +++ b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/JavaClassImpl.kt.201 @@ -0,0 +1,148 @@ +/* + * Copyright 2010-2017 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.load.java.structure.impl + +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiClass +import com.intellij.psi.PsiTypeParameter +import com.intellij.psi.search.SearchScope +import org.jetbrains.kotlin.asJava.KtLightClassMarker +import org.jetbrains.kotlin.asJava.isSyntheticValuesOrValueOfMethod +import org.jetbrains.kotlin.descriptors.Visibility +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtPsiUtil + +class JavaClassImpl(psiClass: PsiClass) : JavaClassifierImpl(psiClass), VirtualFileBoundJavaClass, JavaAnnotationOwnerImpl, JavaModifierListOwnerImpl { + init { + assert(psiClass !is PsiTypeParameter) { "PsiTypeParameter should be wrapped in JavaTypeParameter, not JavaClass: use JavaClassifier.create()" } + } + + override val innerClassNames: Collection + get() = psi.innerClasses.mapNotNull { it.name?.takeIf(Name::isValidIdentifier)?.let(Name::identifier) } + + override fun findInnerClass(name: Name): JavaClass? { + return psi.findInnerClassByName(name.asString(), false)?.let(::JavaClassImpl) + } + + override val fqName: FqName? + get() { + val qualifiedName = psi.qualifiedName + return if (qualifiedName == null) null else FqName(qualifiedName) + } + + override val name: Name + get() = KtPsiUtil.safeName(psi.name) + + override val isInterface: Boolean + get() = psi.isInterface + + override val isAnnotationType: Boolean + get() = psi.isAnnotationType + + override val isEnum: Boolean + get() = psi.isEnum + + override val isRecord: Boolean + get() = false + + override val outerClass: JavaClassImpl? + get() { + val outer = psi.containingClass + return if (outer == null) null else JavaClassImpl(outer) + } + + override val typeParameters: List + get() = typeParameters(psi.typeParameters) + + override val supertypes: Collection + get() = classifierTypes(psi.superTypes) + + override val methods: Collection + get() { + assertNotLightClass() + // We apply distinct here because PsiClass#getMethods() can return duplicate PSI methods, for example in Lombok (see KT-11778) + // Return type seems to be null for example for the 'clone' Groovy method generated by @AutoClone (see EA-73795) + return methods( + psi.methods.filter { method -> + !method.isConstructor && method.returnType != null && !(isEnum && isSyntheticValuesOrValueOfMethod(method)) + } + ).distinct() + } + + override val fields: Collection + get() { + assertNotLightClass() + return fields(psi.fields.filter { + // ex. Android plugin generates LightFields for resources started from '.' (.DS_Store file etc) + Name.isValidIdentifier(it.name) + }) + } + + override val constructors: Collection + get() { + assertNotLightClass() + // See for example org.jetbrains.plugins.scala.lang.psi.light.ScFunctionWrapper, + // which is present in getConstructors(), but its isConstructor() returns false + return constructors(psi.constructors.filter { method -> method.isConstructor }) + } + + override val recordComponents: Collection + get() { + assertNotLightClass() + + return emptyList() + } + + override fun hasDefaultConstructor() = !isInterface && constructors.isEmpty() + + override val isAbstract: Boolean + get() = JavaElementUtil.isAbstract(this) + + override val isStatic: Boolean + get() = JavaElementUtil.isStatic(this) + + override val isFinal: Boolean + get() = JavaElementUtil.isFinal(this) + + override val visibility: Visibility + get() = JavaElementUtil.getVisibility(this) + + override val lightClassOriginKind: LightClassOriginKind? + get() = (psi as? KtLightClassMarker)?.originKind + + override val virtualFile: VirtualFile? + get() = psi.containingFile?.virtualFile + + override fun isFromSourceCodeInScope(scope: SearchScope): Boolean = psi.containingFile.virtualFile in scope + + override fun getAnnotationOwnerPsi() = psi.modifierList + + private fun assertNotLightClass() { + val psiClass = psi + if (psiClass !is KtLightClassMarker) return + + val message = "Querying members of JavaClass created for $psiClass of type ${psiClass::class.java} defined in file ${psiClass.containingFile?.virtualFile?.canonicalPath}" + LOGGER.error(message) + } + + companion object { + private val LOGGER = Logger.getInstance(JavaClassImpl::class.java) + } +} diff --git a/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt.201 b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt.201 new file mode 100644 index 00000000000..b2901f3c74e --- /dev/null +++ b/compiler/resolution.common.jvm/src/org/jetbrains/kotlin/load/java/structure/impl/classFiles/BinaryJavaClass.kt.201 @@ -0,0 +1,226 @@ +/* + * Copyright 2010-2017 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.load.java.structure.impl.classFiles + +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.search.SearchScope +import gnu.trove.THashMap +import org.jetbrains.kotlin.builtins.PrimitiveType +import org.jetbrains.kotlin.load.java.structure.* +import org.jetbrains.kotlin.load.java.structure.impl.VirtualFileBoundJavaClass +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.utils.SmartList +import org.jetbrains.kotlin.utils.addIfNotNull +import org.jetbrains.org.objectweb.asm.* +import java.text.CharacterIterator +import java.text.StringCharacterIterator + +class BinaryJavaClass( + override val virtualFile: VirtualFile, + override val fqName: FqName, + internal val context: ClassifierResolutionContext, + private val signatureParser: BinaryClassSignatureParser, + override var access: Int = 0, + override val outerClass: JavaClass?, + classContent: ByteArray? = null +) : ClassVisitor(ASM_API_VERSION_FOR_CLASS_READING), VirtualFileBoundJavaClass, BinaryJavaModifierListOwner, MapBasedJavaAnnotationOwner { + private lateinit var myInternalName: String + + override val annotations: MutableCollection = SmartList() + override lateinit var typeParameters: List + override lateinit var supertypes: Collection + override val methods = arrayListOf() + override val fields = arrayListOf() + override val constructors = arrayListOf() + override val recordComponents = arrayListOf() + + override fun hasDefaultConstructor() = false // never: all constructors explicit in bytecode + + override val annotationsByFqName by buildLazyValueForMap() + + // Short name of a nested class of this class -> access flags as seen in the InnerClasses attribute value. + // Note that it doesn't include classes mentioned in other InnerClasses attribute values (those which are not nested in this class). + private val ownInnerClassNameToAccess: MutableMap = THashMap() + + override val innerClassNames get() = ownInnerClassNameToAccess.keys + + override val name: Name + get() = fqName.shortName() + + override val isInterface get() = isSet(Opcodes.ACC_INTERFACE) + override val isAnnotationType get() = isSet(Opcodes.ACC_ANNOTATION) + override val isEnum get() = isSet(Opcodes.ACC_ENUM) + + override val isRecord get() = false + + override val lightClassOriginKind: LightClassOriginKind? get() = null + + override fun isFromSourceCodeInScope(scope: SearchScope): Boolean = false + + override fun visitEnd() { + methods.trimToSize() + fields.trimToSize() + constructors.trimToSize() + } + + init { + try { + ClassReader(classContent ?: virtualFile.contentsToByteArray()).accept( + this, + ClassReader.SKIP_CODE or ClassReader.SKIP_FRAMES + ) + } catch (e: Throwable) { + throw IllegalStateException("Could not read class: $virtualFile", e) + } + } + + override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array?): MethodVisitor? { + if (access.isSet(Opcodes.ACC_SYNTHETIC) || access.isSet(Opcodes.ACC_BRIDGE) || name == "") return null + + // skip semi-synthetic enum methods + if (isEnum) { + if (name == "values" && desc.startsWith("()")) return null + if (name == "valueOf" && desc.startsWith("(Ljava/lang/String;)")) return null + } + + val (member, visitor) = BinaryJavaMethodBase.create(name, access, desc, signature, this, context.copyForMember(), signatureParser) + + when (member) { + is JavaMethod -> methods.add(member) + is JavaConstructor -> constructors.add(member) + else -> error("Unexpected member: ${member.javaClass}") + } + + return visitor + } + + override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) { + if (access.isSet(Opcodes.ACC_SYNTHETIC)) return + if (innerName == null || outerName == null) return + + // Do not read InnerClasses attribute values where full name != outer + $ + inner; treat those classes as top level instead. + // This is possible for example for Groovy-generated $Trait$FieldHelper classes. + if (name == "$outerName$$innerName") { + context.addInnerClass(name, outerName, innerName) + + if (myInternalName == outerName) { + ownInnerClassNameToAccess[context.mapInternalNameToClassId(name).shortClassName] = access + } + } + } + + override fun visit( + version: Int, + access: Int, + name: String, + signature: String?, + superName: String?, + interfaces: Array? + ) { + this.access = this.access or access + this.myInternalName = name + + if (signature != null) { + parseClassSignature(signature) + } else { + this.typeParameters = emptyList() + this.supertypes = mutableListOf().apply { + addIfNotNull(superName?.convertInternalNameToClassifierType()) + interfaces?.forEach { + addIfNotNull(it.convertInternalNameToClassifierType()) + } + } + } + } + + private fun parseClassSignature(signature: String) { + val iterator = StringCharacterIterator(signature) + this.typeParameters = + signatureParser + .parseTypeParametersDeclaration(iterator, context) + .also(context::addTypeParameters) + + val supertypes = SmartList() + supertypes.addIfNotNull(signatureParser.parseClassifierRefSignature(iterator, context)) + while (iterator.current() != CharacterIterator.DONE) { + supertypes.addIfNotNull(signatureParser.parseClassifierRefSignature(iterator, context)) + } + this.supertypes = supertypes + } + + private fun String.convertInternalNameToClassifierType(): JavaClassifierType = + PlainJavaClassifierType({ context.resolveByInternalName(this) }, emptyList()) + + override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? { + if (access.isSet(Opcodes.ACC_SYNTHETIC)) return null + + val type = signatureParser.parseTypeString(StringCharacterIterator(signature ?: desc), context) + + val processedValue = processValue(value, type) + + return BinaryJavaField(Name.identifier(name), access, this, access.isSet(Opcodes.ACC_ENUM), type, processedValue).run { + fields.add(this) + + object : FieldVisitor(ASM_API_VERSION_FOR_CLASS_READING) { + override fun visitAnnotation(desc: String, visible: Boolean) = + BinaryJavaAnnotation.addAnnotation(this@run.annotations, desc, context, signatureParser) + + override fun visitTypeAnnotation(typeRef: Int, typePath: TypePath?, desc: String, visible: Boolean) = + if (typePath == null) + BinaryJavaAnnotation.addTypeAnnotation(type, desc, context, signatureParser) + else + null + } + } + } + + /** + * All the int-like values (including Char/Boolean) come in visitor as Integer instances + */ + private fun processValue(value: Any?, fieldType: JavaType): Any? { + if (fieldType !is JavaPrimitiveType || fieldType.type == null || value !is Int) return value + + return when (fieldType.type) { + PrimitiveType.BOOLEAN -> { + when (value) { + 0 -> false + 1 -> true + else -> value + } + } + PrimitiveType.CHAR -> value.toChar() + else -> value + } + } + + override fun visitAnnotation(desc: String, visible: Boolean) = + BinaryJavaAnnotation.addAnnotation(annotations, desc, context, signatureParser) + + override fun findInnerClass(name: Name): JavaClass? = findInnerClass(name, classFileContent = null) + + fun findInnerClass(name: Name, classFileContent: ByteArray?): JavaClass? { + val access = ownInnerClassNameToAccess[name] ?: return null + + return virtualFile.parent.findChild("${virtualFile.nameWithoutExtension}$$name.class")?.let { + BinaryJavaClass( + it, fqName.child(name), context.copyForMember(), signatureParser, access, this, + classFileContent + ) + } + } +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableClinitClassBuilderInterceptorExtension.kt.201 b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableClinitClassBuilderInterceptorExtension.kt.201 new file mode 100644 index 00000000000..4ebe4e21252 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableClinitClassBuilderInterceptorExtension.kt.201 @@ -0,0 +1,156 @@ +/* + * Copyright 2010-2015 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.android.synthetic.codegen + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.android.parcel.isParcelize +import org.jetbrains.kotlin.codegen.AbstractClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilderFactory +import org.jetbrains.kotlin.codegen.DelegatingClassBuilder +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Opcodes.ACC_STATIC +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +class ParcelableClinitClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { + override fun interceptClassBuilderFactory( + interceptedFactory: ClassBuilderFactory, + bindingContext: BindingContext, + diagnostics: DiagnosticSink + ): ClassBuilderFactory { + return ParcelableClinitClassBuilderFactory(interceptedFactory, bindingContext) + } + + private inner class ParcelableClinitClassBuilderFactory( + private val delegateFactory: ClassBuilderFactory, + val bindingContext: BindingContext + ) : ClassBuilderFactory { + + override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder { + return AndroidOnDestroyCollectorClassBuilder(origin, delegateFactory.newClassBuilder(origin), bindingContext) + } + + override fun getClassBuilderMode() = delegateFactory.classBuilderMode + + override fun asText(builder: ClassBuilder?): String? { + return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun asBytes(builder: ClassBuilder?): ByteArray? { + return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun close() { + delegateFactory.close() + } + } + + private inner class AndroidOnDestroyCollectorClassBuilder( + val declarationOrigin: JvmDeclarationOrigin, + internal val delegateClassBuilder: ClassBuilder, + val bindingContext: BindingContext + ) : DelegatingClassBuilder() { + private var currentClass: KtClassOrObject? = null + private var currentClassName: String? = null + private var isClinitGenerated = false + + override fun getDelegate() = delegateClassBuilder + + override fun defineClass( + origin: PsiElement?, + version: Int, + access: Int, + name: String, + signature: String?, + superName: String, + interfaces: Array + ) { + if (origin is KtClassOrObject) { + currentClass = origin + } else { + currentClass = null + } + + currentClassName = name + isClinitGenerated = false + + super.defineClass(origin, version, access, name, signature, superName, interfaces) + } + + override fun done() { + if (!isClinitGenerated && currentClass != null && currentClassName != null) { + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + val baseVisitor = super.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_STATIC, "", "()V", null, null) + val visitor = ClinitAwareMethodVisitor(currentClassName!!, baseVisitor) + + visitor.visitCode() + visitor.visitInsn(Opcodes.RETURN) + visitor.visitEnd() + } + } + + super.done() + } + + override fun newMethod( + origin: JvmDeclarationOrigin, + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + if (name == "" && currentClass != null && currentClassName != null) { + isClinitGenerated = true + + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + return ClinitAwareMethodVisitor( + currentClassName!!, + super.newMethod(origin, access, name, desc, signature, exceptions)) + } + } + + return super.newMethod(origin, access, name, desc, signature, exceptions) + } + } + + private class ClinitAwareMethodVisitor(val parcelableName: String, mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) { + override fun visitInsn(opcode: Int) { + if (opcode == Opcodes.RETURN) { + val iv = InstructionAdapter(this) + val creatorName = "$parcelableName\$Creator" + val creatorType = Type.getObjectType(creatorName) + + iv.anew(creatorType) + iv.dup() + iv.invokespecial(creatorName, "", "()V", false) + iv.putstatic(parcelableName, "CREATOR", "Landroid/os/Parcelable\$Creator;") + } + + super.visitInsn(opcode) + } + } +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt.201 b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt.201 new file mode 100644 index 00000000000..15e71b8c17f --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt.201 @@ -0,0 +1,136 @@ +/* + * Copyright 2010-2015 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.android.synthetic.codegen + +import com.intellij.psi.PsiElement +import kotlinx.android.extensions.CacheImplementation +import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.CLEAR_CACHE_METHOD_NAME +import org.jetbrains.kotlin.android.synthetic.codegen.AbstractAndroidExtensionsExpressionCodegenExtension.Companion.ON_DESTROY_METHOD_NAME +import org.jetbrains.kotlin.android.synthetic.descriptors.ContainerOptionsProxy +import org.jetbrains.kotlin.codegen.AbstractClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilderFactory +import org.jetbrains.kotlin.codegen.DelegatingClassBuilder +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.psi.KtClass +import org.jetbrains.kotlin.psi.KtElement +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +abstract class AbstractAndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { + override fun interceptClassBuilderFactory( + interceptedFactory: ClassBuilderFactory, + bindingContext: BindingContext, + diagnostics: DiagnosticSink + ): ClassBuilderFactory { + return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext) + } + + abstract fun getGlobalCacheImpl(element: KtElement): CacheImplementation + + private inner class AndroidOnDestroyClassBuilderFactory( + private val delegateFactory: ClassBuilderFactory, + val bindingContext: BindingContext + ) : ClassBuilderFactory { + + override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder { + return AndroidOnDestroyCollectorClassBuilder(delegateFactory.newClassBuilder(origin), bindingContext) + } + + override fun getClassBuilderMode() = delegateFactory.classBuilderMode + + override fun asText(builder: ClassBuilder?): String? { + return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun asBytes(builder: ClassBuilder?): ByteArray? { + return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun close() { + delegateFactory.close() + } + } + + private inner class AndroidOnDestroyCollectorClassBuilder( + internal val delegateClassBuilder: ClassBuilder, + val bindingContext: BindingContext + ) : DelegatingClassBuilder() { + private var currentClass: KtClass? = null + private var currentClassName: String? = null + + override fun getDelegate() = delegateClassBuilder + + override fun defineClass( + origin: PsiElement?, + version: Int, + access: Int, + name: String, + signature: String?, + superName: String, + interfaces: Array + ) { + if (origin is KtClass) { + currentClass = origin + currentClassName = name + } + super.defineClass(origin, version, access, name, signature, superName, interfaces) + } + + override fun newMethod( + origin: JvmDeclarationOrigin, + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + return object : MethodVisitor(Opcodes.API_VERSION, super.newMethod(origin, access, name, desc, signature, exceptions)) { + override fun visitInsn(opcode: Int) { + if (opcode == Opcodes.RETURN) { + generateClearCacheMethodCall() + } + + super.visitInsn(opcode) + } + + private fun generateClearCacheMethodCall() { + val currentClass = currentClass + if (name != ON_DESTROY_METHOD_NAME || currentClass == null) return + if (Type.getArgumentTypes(desc).isNotEmpty()) return + if (Type.getReturnType(desc) != Type.VOID_TYPE) return + + val containerType = currentClassName?.let { Type.getObjectType(it) } ?: return + + val container = bindingContext.get(BindingContext.CLASS, currentClass) ?: return + val entityOptions = ContainerOptionsProxy.create(container) + if (!entityOptions.containerType.isFragment || !(entityOptions.cache ?: getGlobalCacheImpl(currentClass)).hasCache) return + + val iv = InstructionAdapter(this) + iv.load(0, containerType) + iv.invokevirtual(currentClassName, CLEAR_CACHE_METHOD_NAME, "()V", false) + } + } + } + } + +} diff --git a/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt.201 b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt.201 new file mode 100644 index 00000000000..3657aebecfb --- /dev/null +++ b/plugins/parcelize/parcelize-compiler/src/org/jetbrains/kotlin/parcelize/ParcelizeClinitClassBuilderInterceptorExtension.kt.201 @@ -0,0 +1,150 @@ +/* + * Copyright 2010-2015 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.parcelize + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.codegen.AbstractClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilder +import org.jetbrains.kotlin.codegen.ClassBuilderFactory +import org.jetbrains.kotlin.codegen.DelegatingClassBuilder +import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.psi.KtClassOrObject +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.MethodVisitor +import org.jetbrains.org.objectweb.asm.Opcodes +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +class ParcelizeClinitClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { + override fun interceptClassBuilderFactory( + interceptedFactory: ClassBuilderFactory, + bindingContext: BindingContext, + diagnostics: DiagnosticSink + ): ClassBuilderFactory { + return ParcelableClinitClassBuilderFactory(interceptedFactory, bindingContext) + } + + private inner class ParcelableClinitClassBuilderFactory( + private val delegateFactory: ClassBuilderFactory, + val bindingContext: BindingContext + ) : ClassBuilderFactory { + + override fun newClassBuilder(origin: JvmDeclarationOrigin): ClassBuilder { + return AndroidOnDestroyCollectorClassBuilder(origin, delegateFactory.newClassBuilder(origin), bindingContext) + } + + override fun getClassBuilderMode() = delegateFactory.classBuilderMode + + override fun asText(builder: ClassBuilder?): String? { + return delegateFactory.asText((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun asBytes(builder: ClassBuilder?): ByteArray? { + return delegateFactory.asBytes((builder as AndroidOnDestroyCollectorClassBuilder).delegateClassBuilder) + } + + override fun close() { + delegateFactory.close() + } + } + + private class AndroidOnDestroyCollectorClassBuilder( + val declarationOrigin: JvmDeclarationOrigin, + val delegateClassBuilder: ClassBuilder, + val bindingContext: BindingContext + ) : DelegatingClassBuilder() { + private var currentClass: KtClassOrObject? = null + private var currentClassName: String? = null + private var isClinitGenerated = false + + override fun getDelegate() = delegateClassBuilder + + override fun defineClass( + origin: PsiElement?, + version: Int, + access: Int, + name: String, + signature: String?, + superName: String, + interfaces: Array + ) { + currentClass = origin as? KtClassOrObject + currentClassName = name + isClinitGenerated = false + + super.defineClass(origin, version, access, name, signature, superName, interfaces) + } + + override fun done() { + if (!isClinitGenerated && currentClass != null && currentClassName != null) { + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + val baseVisitor = super.newMethod(JvmDeclarationOrigin.NO_ORIGIN, Opcodes.ACC_STATIC, "", "()V", null, null) + val visitor = ClinitAwareMethodVisitor(currentClassName!!, baseVisitor) + + visitor.visitCode() + visitor.visitInsn(Opcodes.RETURN) + visitor.visitEnd() + } + } + + super.done() + } + + override fun newMethod( + origin: JvmDeclarationOrigin, + access: Int, + name: String, + desc: String, + signature: String?, + exceptions: Array? + ): MethodVisitor { + if (name == "" && currentClass != null && currentClassName != null) { + isClinitGenerated = true + + val descriptor = bindingContext[BindingContext.CLASS, currentClass] + if (descriptor != null && declarationOrigin.descriptor == descriptor && descriptor.isParcelize) { + return ClinitAwareMethodVisitor( + currentClassName!!, + super.newMethod(origin, access, name, desc, signature, exceptions) + ) + } + } + + return super.newMethod(origin, access, name, desc, signature, exceptions) + } + } + + private class ClinitAwareMethodVisitor(val parcelableName: String, mv: MethodVisitor) : MethodVisitor(Opcodes.API_VERSION, mv) { + override fun visitInsn(opcode: Int) { + if (opcode == Opcodes.RETURN) { + val iv = InstructionAdapter(this) + val creatorName = "$parcelableName\$Creator" + val creatorType = Type.getObjectType(creatorName) + + iv.anew(creatorType) + iv.dup() + iv.invokespecial(creatorName, "", "()V", false) + iv.putstatic(parcelableName, "CREATOR", "Landroid/os/Parcelable\$Creator;") + } + + super.visitInsn(opcode) + } + } +}