Rewrite mutable collection stub method generation

The main problem of the previous approach was that we were only generating
erased method signatures, which was incorrect in case a class also had a member
from another supertype with the same signature as the substituted one from the
collection. Javac issues compilation errors when compiling Java code against
such classes.

Also all the needed method stub signatures were hardcoded in
generateBuiltInMethodStubs() and the case of MutableListIterator was missing
This commit is contained in:
Alexander Udalov
2014-10-22 16:48:30 +04:00
parent 1fc9c58b24
commit 35e956609a
25 changed files with 795 additions and 112 deletions
@@ -0,0 +1,232 @@
/*
* Copyright 2010-2014 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.jet.codegen
import org.jetbrains.jet.codegen.state.GenerationState
import org.jetbrains.jet.lang.descriptors.*
import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor.Kind.*
import org.jetbrains.jet.lang.descriptors.impl.*
import org.jetbrains.org.objectweb.asm.Opcodes.*
import org.jetbrains.jet.lang.resolve.java.diagnostics.JvmDeclarationOrigin
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.types.*
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.lang.descriptors.annotations.Annotations
import org.jetbrains.jet.lang.resolve.OverrideResolver
import org.jetbrains.jet.lang.resolve.OverridingUtil
import java.util.LinkedHashSet
import org.jetbrains.jet.lang.resolve.name.Name
import java.util.ArrayList
import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import java.util.HashSet
/**
* Generates exception-throwing stubs for methods from mutable collection classes not implemented in Kotlin classes which inherit only from
* Kotlin's read-only collections. This is required on JVM because Kotlin's read-only collections are mapped to mutable JDK collections
*/
class CollectionStubMethodGenerator(
state: GenerationState,
private val descriptor: ClassDescriptor,
private val functionCodegen: FunctionCodegen,
private val v: ClassBuilder
) {
private val typeMapper = state.getTypeMapper()
fun generate() {
val superCollectionClasses = findRelevantSuperCollectionClasses()
if (superCollectionClasses.isEmpty()) return
val methodStubsToGenerate = LinkedHashSet<String>()
val syntheticStubsToGenerate = LinkedHashSet<String>()
for ((readOnlyClass, mutableClass) in superCollectionClasses) {
// To determine which method stubs we need to generate, we create a synthetic class (named 'child' here) which inherits from
// our class ('descriptor') and the corresponding MutableCollection class (for example; the process is the same for every
// built-in read-only/mutable class pair). We then construct and bind fake overrides in this synthetic class with the usual
// override resolution process. Resulting fake overrides with originals in MutableCollection are considered as candidates for
// method stubs or bridges to the actual implementation that happened to be present in the class
val (child, typeParameters) = createSyntheticSubclass()
// If the original class has any type parameters, we copied them and now we need to substitute types of the newly created type
// parameters as arguments for the type parameters of the original class
val parentType = newType(descriptor, typeParameters.map { TypeProjectionImpl(it.getDefaultType()) })
// Now we need to determine the arguments which should be substituted for the MutableCollection super class. To do that,
// we look for type arguments which were substituted in the inheritance of the original class from Collection and use them
// to construct the needed MutableCollection type. Since getAllSupertypes() may return several types which correspond to the
// Collection class descriptor, we find the most specific one (which is guaranteed to exist by front-end)
val readOnlyCollectionType = TypeUtils.getAllSupertypes(parentType).findMostSpecificTypeForClass(readOnlyClass)
val mutableCollectionType = newType(mutableClass, readOnlyCollectionType.getArguments())
child.addSupertype(parentType)
child.addSupertype(mutableCollectionType)
child.createTypeConstructor()
// Bind fake overrides and for each fake override originated from the MutableCollection, save its signature to generate a stub
// or save its descriptor to generate all the needed bridges
for (method in findFakeOverridesForMethodsFromMutableCollection(child, mutableClass)) {
if (method.getModality() == Modality.ABSTRACT) {
// If the fake override is abstract and it's _declared_ as abstract in the class, skip it because the method is already
// present in the bytecode (abstract) and we don't want a duplicate signature error
if (method.findOverriddenFromDirectSuperClass(descriptor)?.getKind() == DECLARATION) continue
// Otherwise we can safely generate the stub with the substituted signature
val signature = method.signature()
methodStubsToGenerate.add(signature)
// If the substituted signature differs from the original one in MutableCollection, we should also generate a stub with
// the original (erased) signature. It doesn't really matter if this is a bridge method delegating to the first stub or
// a method with its own exception-throwing code, for simplicity we do the latter here.
// What _does_ matter though, is that these two methods can't be both non-synthetic at once: javac issues compilation
// errors when compiling Java against such classes because one of them doesn't seem to override the generic method
// declared in the Java Collection interface (can't override generic with erased). So we maintain an additional set of
// methods which need to be generated with the ACC_SYNTHETIC flag
val originalSignature = method.findOverriddenFromDirectSuperClass(mutableClass)!!.getOriginal().signature()
if (originalSignature != signature) {
syntheticStubsToGenerate.add(originalSignature)
}
}
else {
// If the fake override is non-abstract, its implementation is already present in the class or inherited from one of its
// super classes, but is not related to the MutableCollection hierarchy. So maybe it uses more specific return types
// and we may need to generate some bridges
functionCodegen.generateBridges(method)
}
}
}
for (signature in methodStubsToGenerate) {
generateMethodStub(signature, synthetic = false)
}
for (signature in syntheticStubsToGenerate) {
generateMethodStub(signature, synthetic = true)
}
}
private data class CollectionClassPair(
val readOnlyClass: ClassDescriptor,
val mutableClass: ClassDescriptor
)
private fun findRelevantSuperCollectionClasses(): Collection<CollectionClassPair> {
fun pair(readOnlyClass: ClassDescriptor, mutableClass: ClassDescriptor) = CollectionClassPair(readOnlyClass, mutableClass)
val collectionClasses = with(KotlinBuiltIns.getInstance()) {
listOf(
pair(getCollection(), getMutableCollection()),
pair(getSet(), getMutableSet()),
pair(getList(), getMutableList()),
pair(getMap(), getMutableMap()),
pair(getMapEntry(), getMutableMapEntry()),
pair(getIterable(), getMutableIterable()),
pair(getIterator(), getMutableIterator()),
pair(getListIterator(), getMutableListIterator())
)
}
val allSuperClasses = TypeUtils.getAllSupertypes(descriptor.getDefaultType()).classes().toHashSet()
val ourSuperCollectionClasses = collectionClasses.filter { pair ->
pair.readOnlyClass in allSuperClasses && pair.mutableClass !in allSuperClasses
}
if (ourSuperCollectionClasses.isEmpty()) return listOf()
// Filter out built-in classes which are overridden by other built-in classes in the list, to avoid duplicating methods.
val redundantClasses = ourSuperCollectionClasses.flatMapTo(HashSet<ClassDescriptor>()) { pair ->
pair.readOnlyClass.getTypeConstructor().getSupertypes().classes()
}
return ourSuperCollectionClasses.filter { klass -> klass.readOnlyClass !in redundantClasses }
}
private fun Collection<JetType>.classes(): Collection<ClassDescriptor> =
this.map { it.getConstructor().getDeclarationDescriptor() as ClassDescriptor }
private fun findFakeOverridesForMethodsFromMutableCollection(
klass: ClassDescriptor,
mutableCollectionClass: ClassDescriptor
): List<FunctionDescriptor> {
val result = ArrayList<FunctionDescriptor>()
OverrideResolver.generateOverridesInAClass(klass, listOf(), object : OverridingUtil.DescriptorSink {
override fun addToScope(fakeOverride: CallableMemberDescriptor) {
if (fakeOverride !is FunctionDescriptor) return
if (fakeOverride.findOverriddenFromDirectSuperClass(mutableCollectionClass) != null) {
result.add(fakeOverride)
}
}
override fun conflict(fromSuper: CallableMemberDescriptor, fromCurrent: CallableMemberDescriptor) {
// Ignore conflicts here
// TODO: report a warning that javac will prohibit use/inheritance from such class
}
})
return result
}
private fun Collection<JetType>.findMostSpecificTypeForClass(klass: ClassDescriptor): JetType {
val types = this.filter { it.getConstructor().getDeclarationDescriptor() == klass }
if (types.isEmpty()) error("No supertype of $klass in $this")
if (types.size == 1) return types.first()
// Find the first type in the list such that it's a subtype of every other type in that list
return types.first { type ->
types.all { other -> JetTypeChecker.DEFAULT.isSubtypeOf(type, other) }
}
}
private fun createSyntheticSubclass(): Pair<MutableClassDescriptor, List<TypeParameterDescriptor>> {
val child = MutableClassDescriptor(descriptor.getContainingDeclaration(), JetScope.EMPTY, ClassKind.CLASS, false,
Name.special("<synthetic inheritor of ${descriptor.getName()}>"), descriptor.getSource())
child.setModality(Modality.FINAL)
child.setVisibility(Visibilities.PUBLIC)
val typeParameters = descriptor.getTypeConstructor().getParameters()
val newTypeParameters = ArrayList<TypeParameterDescriptor>(typeParameters.size())
DescriptorSubstitutor.substituteTypeParameters(typeParameters, TypeSubstitutor.EMPTY, child, newTypeParameters)
child.setTypeParameterDescriptors(typeParameters)
return Pair(child, newTypeParameters)
}
private fun FunctionDescriptor.findOverriddenFromDirectSuperClass(classDescriptor: ClassDescriptor): FunctionDescriptor? {
return this.getOverriddenDescriptors().firstOrNull { it.getContainingDeclaration() == classDescriptor }
}
private fun newType(classDescriptor: ClassDescriptor, typeArguments: List<TypeProjection>): JetType {
return JetTypeImpl(Annotations.EMPTY, classDescriptor.getTypeConstructor(), false, typeArguments,
classDescriptor.getMemberScope(typeArguments))
}
private fun FunctionDescriptor.signature(): String {
val method = typeMapper.mapSignature(this).getAsmMethod()
return method.getName() + method.getDescriptor()
}
private fun generateMethodStub(signature: String, synthetic: Boolean) {
// TODO: investigate if it makes sense to generate abstract stubs in traits
var access = ACC_PUBLIC
if (descriptor.getKind() == ClassKind.TRAIT) access = access or ACC_ABSTRACT
if (synthetic) access = access or ACC_SYNTHETIC
val paren = signature.indexOf('(')
val mv = v.newMethod(JvmDeclarationOrigin.NO_ORIGIN, access, signature.substring(0, paren), signature.substring(paren), null, null)
if (descriptor.getKind() != ClassKind.TRAIT) {
mv.visitCode()
AsmUtil.genThrow(InstructionAdapter(mv), "java/lang/UnsupportedOperationException", "Mutating immutable collection")
FunctionCodegen.endVisit(mv, "built-in stub for $signature", null)
}
}
}
@@ -427,7 +427,7 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
generateFunctionsForDataClasses();
generateBuiltinMethodStubs();
new CollectionStubMethodGenerator(state, descriptor, functionCodegen, v).generate();
generateToArray();
@@ -510,82 +510,6 @@ public class ImplementationBodyCodegen extends ClassBodyCodegen {
}
}
@NotNull
private Set<String> collectSignaturesOfExistingNonAbstractMethods() {
Set<String> existingMethodSignatures = new HashSet<String>();
for (DeclarationDescriptor member : descriptor.getDefaultType().getMemberScope().getAllDescriptors()) {
if (!(member instanceof FunctionDescriptor)) continue;
FunctionDescriptor function = (FunctionDescriptor) member;
if (function.getModality() == Modality.ABSTRACT) continue;
for (FunctionDescriptor overridden : DescriptorUtils.getAllOverriddenDescriptors(function)) {
Method overriddenMethod = typeMapper.mapSignature(overridden.getOriginal()).getAsmMethod();
existingMethodSignatures.add(overriddenMethod.getName() + overriddenMethod.getDescriptor());
}
Method method = typeMapper.mapSignature(function.getOriginal()).getAsmMethod();
existingMethodSignatures.add(method.getName() + method.getDescriptor());
}
return existingMethodSignatures;
}
private void generateMethodStubs(@NotNull Set<String> signatures) {
if (signatures.isEmpty()) return;
Set<String> existingMethodSignatures = collectSignaturesOfExistingNonAbstractMethods();
for (String signature : signatures) {
if (!existingMethodSignatures.add(signature)) return;
int access = descriptor.getKind() == ClassKind.TRAIT ?
ACC_PUBLIC | ACC_ABSTRACT :
ACC_PUBLIC;
int paren = signature.indexOf('(');
MethodVisitor mv = v.newMethod(NO_ORIGIN, access, signature.substring(0, paren), signature.substring(paren), null, null);
if (descriptor.getKind() != ClassKind.TRAIT) {
mv.visitCode();
genThrow(new InstructionAdapter(mv), "java/lang/UnsupportedOperationException", "Mutating immutable collection");
FunctionCodegen.endVisit(mv, "built-in stub for " + signature, null);
}
}
}
private void generateBuiltinMethodStubs() {
KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance();
Set<String> methodStubs = new LinkedHashSet<String>();
if (isSubclass(descriptor, builtIns.getCollection())) {
methodStubs.add("add(Ljava/lang/Object;)Z");
methodStubs.add("remove(Ljava/lang/Object;)Z");
methodStubs.add("addAll(Ljava/util/Collection;)Z");
methodStubs.add("removeAll(Ljava/util/Collection;)Z");
methodStubs.add("retainAll(Ljava/util/Collection;)Z");
methodStubs.add("clear()V");
}
if (isSubclass(descriptor, builtIns.getList())) {
methodStubs.add("set(ILjava/lang/Object;)Ljava/lang/Object;");
methodStubs.add("add(ILjava/lang/Object;)V");
methodStubs.add("remove(I)Ljava/lang/Object;");
}
if (isSubclass(descriptor, builtIns.getMap())) {
methodStubs.add("put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
methodStubs.add("remove(Ljava/lang/Object;)Ljava/lang/Object;");
methodStubs.add("putAll(Ljava/util/Map;)V");
methodStubs.add("clear()V");
}
if (isSubclass(descriptor, builtIns.getMapEntry())) {
methodStubs.add("setValue(Ljava/lang/Object;)Ljava/lang/Object;");
}
if (isSubclass(descriptor, builtIns.getIterator())) {
methodStubs.add("remove()V");
}
generateMethodStubs(methodStubs);
}
private void generateFunctionsForDataClasses() {
if (!KotlinBuiltIns.getInstance().isData(descriptor)) return;