From fbc769b0e93d526e91e6391119506446dca0911c Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Tue, 8 Sep 2015 19:27:59 +0300 Subject: [PATCH] Clear cache on fragment onDestroy() --- .../ClassBuilderInterceptorExtension.kt | 2 +- idea/src/META-INF/extensions/common.xml | 2 +- .../synthetic/AndroidComponentRegistrar.kt | 3 + .../AndroidExpressionCodegenExtension.kt | 161 +++++++++++------- ...DestroyClassBuilderInterceptorExtension.kt | 118 +++++++++++++ .../onDestroyFragment/onDestroyFragment.kt | 23 +++ .../onDestroyFragment/res/layout/layout.xml | 17 ++ .../AndroidBytecodeShapeTestGenerated.java | 6 + .../resolve/android/test/CompilerTestUtils.kt | 6 + .../src/META-INF/plugin.xml | 1 + 10 files changed, 272 insertions(+), 67 deletions(-) create mode 100644 plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt create mode 100644 plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/onDestroyFragment.kt create mode 100644 plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/res/layout/layout.xml diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/extensions/ClassBuilderInterceptorExtension.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/extensions/ClassBuilderInterceptorExtension.kt index 5b22f97d31a..5d9b45ea4a0 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/extensions/ClassBuilderInterceptorExtension.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/extensions/ClassBuilderInterceptorExtension.kt @@ -23,7 +23,7 @@ import org.jetbrains.kotlin.resolve.BindingContext public interface ClassBuilderInterceptorExtension { companion object : ProjectExtensionDescriptor( - "org.jetbrains.kotlin.classBuilderFactoryInterceptorExtension", javaClass()) + "org.jetbrains.kotlin.classBuilderFactoryInterceptorExtension", ClassBuilderInterceptorExtension::class.java) public fun interceptClassBuilderFactory( interceptedFactory: ClassBuilderFactory, diff --git a/idea/src/META-INF/extensions/common.xml b/idea/src/META-INF/extensions/common.xml index 800ebe14a84..2ac1a18c52a 100644 --- a/idea/src/META-INF/extensions/common.xml +++ b/idea/src/META-INF/extensions/common.xml @@ -20,7 +20,7 @@ - AndroidClassType.ACTIVITY + AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT + AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY + AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT + AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW + else -> null + } + + if (descriptor is LazyJavaClassDescriptor) { + val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).asString()) + if (androidClassType != null) return androidClassType + } + else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity) + val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString()) + if (androidClassType != null) return androidClassType + } + + for (supertype in descriptor.typeConstructor.supertypes) { + val declarationDescriptor = supertype.constructor.declarationDescriptor + if (declarationDescriptor != null) { + val androidClassType = getClassType(declarationDescriptor) + if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType + } + } + + return AndroidClassType.UNKNOWN + } + } +} public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { companion object { private val PROPERTY_NAME = "_\$_findViewCache" private val CACHED_FIND_VIEW_BY_ID_METHOD_NAME = "_\$_findCachedViewById" - private val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" + val CLEAR_CACHE_METHOD_NAME = "_\$_clearFindViewByIdCache" + val ON_DESTROY_METHOD_NAME = "onDestroy" - fun isCacheSupported(descriptor: ClassifierDescriptor) = descriptor.getSource() is KotlinSourceElement + fun isCacheSupported(descriptor: ClassifierDescriptor) = descriptor.source is KotlinSourceElement } private class SyntheticPartsGenerateContext( @@ -65,12 +98,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { val state: GenerationState, val descriptor: ClassDescriptor, val classOrObject: JetClassOrObject, - val androidClassType: AndroidClassType) { - - } + val androidClassType: AndroidClassType) override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? { - val resultingDescriptor = resolvedCall.getResultingDescriptor() + val resultingDescriptor = resolvedCall.resultingDescriptor return if (resultingDescriptor is PropertyDescriptor) { return generateSyntheticPropertyCall(receiver, resolvedCall, c, resultingDescriptor) } @@ -78,7 +109,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { } override fun applyFunction(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): Boolean { - val resultingDescriptor = resolvedCall.getResultingDescriptor() + val resultingDescriptor = resolvedCall.resultingDescriptor return if (resultingDescriptor is FunctionDescriptor) { return generateSyntheticFunctionCall(receiver, resolvedCall, c, resultingDescriptor) } @@ -92,15 +123,15 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { descriptor: FunctionDescriptor ): Boolean { if (descriptor.getAndroidPackage() == null) return false - if (descriptor.getName().asString() != AndroidConst.CLEAR_FUNCTION_NAME) return false + if (descriptor.name.asString() != AndroidConst.CLEAR_FUNCTION_NAME) return false val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return false if (!isCacheSupported(declarationDescriptor)) return true - val androidClassType = getClassType(declarationDescriptor) + val androidClassType = AndroidClassType.getClassType(declarationDescriptor) if (androidClassType == AndroidClassType.UNKNOWN) return false - val bytecodeClassName = DescriptorUtils.getFqName(declarationDescriptor).asString().replace('.', '/') + val bytecodeClassName = c.typeMapper.mapType(declarationDescriptor).internalName receiver.put(c.typeMapper.mapType(declarationDescriptor), c.v) c.v.invokevirtual(bytecodeClassName, CLEAR_CACHE_METHOD_NAME, "()V", false) @@ -116,8 +147,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { ): StackValue? { val androidPackage = descriptor.getAndroidPackage() ?: return null val declarationDescriptor = resolvedCall.getReceiverDeclarationDescriptor() ?: return null - val androidClassType = getClassType(declarationDescriptor) - + val androidClassType = AndroidClassType.getClassType(declarationDescriptor) return SyntheticProperty(receiver, c.typeMapper, descriptor, declarationDescriptor, androidClassType, androidPackage) } @@ -129,10 +159,10 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { val declarationDescriptor: ClassifierDescriptor, val androidClassType: AndroidClassType, val androidPackage: String - ) : StackValue(typeMapper.mapType(descriptor.getReturnType()!!)) { + ) : StackValue(typeMapper.mapType(descriptor.returnType!!)) { override fun putSelector(type: Type, v: InstructionAdapter) { - val returnTypeString = typeMapper.mapType(descriptor.getType().lowerIfFlexible()).getClassName() + val returnTypeString = typeMapper.mapType(descriptor.type.lowerIfFlexible()).className if (AndroidConst.FRAGMENT_FQNAME == returnTypeString || AndroidConst.SUPPORT_FRAGMENT_FQNAME == returnTypeString) { return putSelectorForFragment(v) } @@ -140,8 +170,8 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { if (androidClassType.supportsCache && isCacheSupported(declarationDescriptor)) { val declarationDescriptorType = typeMapper.mapType(declarationDescriptor) receiver.put(declarationDescriptorType, v) - v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") - v.invokevirtual(declarationDescriptorType.getInternalName(), CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false) + v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I") + v.invokevirtual(declarationDescriptorType.internalName, CACHED_FIND_VIEW_BY_ID_METHOD_NAME, "(I)Landroid/view/View;", false) } else { when (androidClassType) { @@ -189,7 +219,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { } fun getResourceId(v: InstructionAdapter) { - v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.getName().asString(), "I") + v.getstatic(androidPackage.replace(".", "/") + "/R\$id", descriptor.name.asString(), "I") } } @@ -198,37 +228,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { } private fun ResolvedCall<*>.getReceiverDeclarationDescriptor(): ClassifierDescriptor? { - return getExtensionReceiver().getType().getConstructor().getDeclarationDescriptor() - } - - private fun getClassType(descriptor: ClassifierDescriptor): AndroidClassType { - fun getClassTypeInternal(name: String): AndroidClassType? = when (name) { - AndroidConst.ACTIVITY_FQNAME -> AndroidClassType.ACTIVITY - AndroidConst.FRAGMENT_FQNAME -> AndroidClassType.FRAGMENT - AndroidConst.SUPPORT_FRAGMENT_ACTIVITY_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT_ACTIVITY - AndroidConst.SUPPORT_FRAGMENT_FQNAME -> AndroidClassType.SUPPORT_FRAGMENT - AndroidConst.VIEW_FQNAME -> AndroidClassType.VIEW - else -> null - } - - if (descriptor is LazyJavaClassDescriptor) { - val androidClassType = getClassTypeInternal(descriptor.fqName.asString()) - if (androidClassType != null) return androidClassType - } - else if (descriptor is LazyClassDescriptor) { // For tests (FakeActivity) - val androidClassType = getClassTypeInternal(DescriptorUtils.getFqName(descriptor).toString()) - if (androidClassType != null) return androidClassType - } - - for (supertype in descriptor.getTypeConstructor().getSupertypes()) { - val declarationDescriptor = supertype.getConstructor().getDeclarationDescriptor() - if (declarationDescriptor != null) { - val androidClassType = getClassType(declarationDescriptor) - if (androidClassType != AndroidClassType.UNKNOWN) return androidClassType - } - } - - return AndroidClassType.UNKNOWN + return extensionReceiver.type.constructor.declarationDescriptor } override fun generateClassSyntheticParts( @@ -237,27 +237,58 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { classOrObject: JetClassOrObject, descriptor: ClassDescriptor ) { - if (descriptor.getKind() != ClassKind.CLASS || descriptor.isInner() || DescriptorUtils.isLocal(descriptor)) return + if (descriptor.kind != ClassKind.CLASS || descriptor.isInner || DescriptorUtils.isLocal(descriptor)) return // Do not generate anything if class is not supported - val androidClassType = getClassType(descriptor) + val androidClassType = AndroidClassType.getClassType(descriptor) if (androidClassType == AndroidClassType.UNKNOWN) return val context = SyntheticPartsGenerateContext(classBuilder, state, descriptor, classOrObject, androidClassType) context.generateCachedFindViewByIdFunction() context.generateClearCacheFunction() + if (androidClassType.fragment) { + val classMembers = descriptor.unsubstitutedMemberScope.getAllDescriptors() + val onDestroy = classMembers.firstOrNull { it is FunctionDescriptor && it.isOnDestroyFunction() } + if (onDestroy == null) { + context.generateOnDestroyFunctionForFragment() + } + } + classBuilder.newField(JvmDeclarationOrigin.NO_ORIGIN, ACC_PRIVATE, PROPERTY_NAME, "Ljava/util/HashMap;", null, null) } - private fun SyntheticPartsGenerateContext.generateClearCacheFunction() { - val methodVisitor = classBuilder.newMethod( - JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CLEAR_CACHE_METHOD_NAME, "()V", null, null) + private fun FunctionDescriptor.isOnDestroyFunction(): Boolean { + return kind == CallableMemberDescriptor.Kind.DECLARATION + && name.asString() == ON_DESTROY_METHOD_NAME + && (visibility == Visibilities.INHERITED || visibility == Visibilities.PUBLIC) + && (valueParameters.isEmpty()) + && (typeParameters.isEmpty()) + } + + // This generates a simple onDestroy(): Unit = super.onDestroy() function. + // CLEAR_CACHE_METHOD_NAME() method call will be inserted in ClassBuilder interceptor. + private fun SyntheticPartsGenerateContext.generateOnDestroyFunctionForFragment() { + val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, ON_DESTROY_METHOD_NAME, "()V", null, null) methodVisitor.visitCode() val iv = InstructionAdapter(methodVisitor) val classType = state.typeMapper.mapClass(descriptor) - val className = classType.getInternalName() + + iv.load(0, classType) + iv.invokespecial(state.typeMapper.mapClass(descriptor.getSuperClassOrAny()).internalName, ON_DESTROY_METHOD_NAME, "()V", false) + iv.areturn(Type.VOID_TYPE) + + FunctionCodegen.endVisit(methodVisitor, ON_DESTROY_METHOD_NAME, classOrObject) + } + + private fun SyntheticPartsGenerateContext.generateClearCacheFunction() { + val methodVisitor = classBuilder.newMethod(JvmDeclarationOrigin.NO_ORIGIN, ACC_PUBLIC, CLEAR_CACHE_METHOD_NAME, "()V", null, null) + methodVisitor.visitCode() + val iv = InstructionAdapter(methodVisitor) + + val classType = state.typeMapper.mapClass(descriptor) + val className = classType.internalName fun loadCache() { iv.load(0, classType) @@ -278,7 +309,7 @@ public class AndroidExpressionCodegenExtension : ExpressionCodegenExtension { private fun SyntheticPartsGenerateContext.generateCachedFindViewByIdFunction() { val classType = state.typeMapper.mapClass(descriptor) - val className = classType.getInternalName() + val className = classType.internalName val viewType = Type.getObjectType("android/view/View") diff --git a/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt new file mode 100644 index 00000000000..6671b95eb5b --- /dev/null +++ b/plugins/android-compiler-plugin/src/org/jetbrains/kotlin/android/synthetic/codegen/AndroidOnDestroyClassBuilderInterceptorExtension.kt @@ -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.android.synthetic.codegen + +import com.intellij.psi.PsiElement +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.descriptors.ClassDescriptor +import org.jetbrains.kotlin.diagnostics.DiagnosticSink +import org.jetbrains.kotlin.psi.JetClass +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin +import org.jetbrains.org.objectweb.asm.* +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter + +public class AndroidOnDestroyClassBuilderInterceptorExtension : ClassBuilderInterceptorExtension { + + override fun interceptClassBuilderFactory( + interceptedFactory: ClassBuilderFactory, + bindingContext: BindingContext, + diagnostics: DiagnosticSink + ): ClassBuilderFactory { + return AndroidOnDestroyClassBuilderFactory(interceptedFactory, bindingContext) + } + + 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.getClassBuilderMode() + + 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: JetClass? = 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 JetClass) { + 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.ASM5, super.newMethod(origin, access, name, desc, signature, exceptions)) { + override fun visitCode() { + super.visitCode() + + if (name != AndroidExpressionCodegenExtension.ON_DESTROY_METHOD_NAME || currentClass == null) return + if (Type.getArgumentTypes(desc).size() != 0) return + if (Type.getReturnType(desc) != Type.VOID_TYPE) return + + val classType = currentClassName?.let { Type.getObjectType(it) } ?: return + + val descriptor = bindingContext.get(BindingContext.CLASS, currentClass) as? ClassDescriptor ?: return + val androidClassType = AndroidClassType.getClassType(descriptor) + if (!androidClassType.fragment) return + + val iv = InstructionAdapter(this) + iv.load(0, classType) + iv.invokevirtual(currentClassName, AndroidExpressionCodegenExtension.CLEAR_CACHE_METHOD_NAME, "()V", false) + } + } + } + } + +} \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/onDestroyFragment.kt b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/onDestroyFragment.kt new file mode 100644 index 00000000000..fb412e7994a --- /dev/null +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/onDestroyFragment.kt @@ -0,0 +1,23 @@ +package com.myapp + +import android.app.Fragment +import java.io.File +import kotlinx.android.synthetic.layout.* + +public class MyFragment : Fragment() { + init {login} +} + +public class MyFragment2 : Fragment() { + + override fun onDestroy() { + super.onDestroy() + } + + public open fun onDestroy(n: Int) {} + +} + +// 2 public onDestroy\(\)V +// 1 INVOKEVIRTUAL com/myapp/MyFragment\._\$_clearFindViewByIdCache +// 1 INVOKEVIRTUAL com/myapp/MyFragment2\._\$_clearFindViewByIdCache \ No newline at end of file diff --git a/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/res/layout/layout.xml b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/res/layout/layout.xml new file mode 100644 index 00000000000..4d73173b521 --- /dev/null +++ b/plugins/android-compiler-plugin/testData/codegen/bytecodeShape/onDestroyFragment/res/layout/layout.xml @@ -0,0 +1,17 @@ + + + + +