diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/GeneratedClassLoader.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/GeneratedClassLoader.java index 52bb7ad57c9..14282bd82ba 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/GeneratedClassLoader.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/GeneratedClassLoader.java @@ -25,11 +25,11 @@ import java.util.List; import java.util.jar.Manifest; public class GeneratedClassLoader extends URLClassLoader { - private ClassFileFactory state; + private ClassFileFactory factory; - public GeneratedClassLoader(@NotNull ClassFileFactory state, ClassLoader parentClassLoader, URL...urls) { + public GeneratedClassLoader(@NotNull ClassFileFactory factory, ClassLoader parentClassLoader, URL... urls) { super(urls, parentClassLoader); - this.state = state; + this.factory = factory; } @NotNull @@ -37,7 +37,7 @@ public class GeneratedClassLoader extends URLClassLoader { protected Class findClass(@NotNull String name) throws ClassNotFoundException { String classFilePath = name.replace('.', '/') + ".class"; - OutputFile outputFile = state.get(classFilePath); + OutputFile outputFile = factory.get(classFilePath); if (outputFile != null) { byte[] bytes = outputFile.asByteArray(); int lastDot = name.lastIndexOf('.'); @@ -54,11 +54,11 @@ public class GeneratedClassLoader extends URLClassLoader { } public void dispose() { - state = null; + factory = null; } @NotNull public List getAllGeneratedFiles() { - return state.asList(); + return factory.asList(); } } diff --git a/compiler/testData/codegen/reflection/classLoaders/differentClassLoaders.kt b/compiler/testData/codegen/reflection/classLoaders/differentClassLoaders.kt new file mode 100644 index 00000000000..6e2081d84d8 --- /dev/null +++ b/compiler/testData/codegen/reflection/classLoaders/differentClassLoaders.kt @@ -0,0 +1,23 @@ +package test + +import kotlin.reflect.KClass +import kotlin.test.* + +class K(val p: String) + +class Test { + fun kClass(): Any = K::class + + fun doTest(k1: KClass<*>, k2: KClass<*>) { + // KClass instances for classes loaded with different class loaders should have the same string representation, + // but should not be equal + assertEquals("$k1", "$k2") + assertNotEquals(k1, k2) + + // The same for properties of these classes + val p1 = k1.properties.first() + val p2 = k2.properties.first() + assertEquals("$p1", "$p2") + assertNotEquals(p1, p2) + } +} diff --git a/compiler/testData/codegen/reflection/classLoaders/parentFirst.kt b/compiler/testData/codegen/reflection/classLoaders/parentFirst.kt new file mode 100644 index 00000000000..75454451e05 --- /dev/null +++ b/compiler/testData/codegen/reflection/classLoaders/parentFirst.kt @@ -0,0 +1,15 @@ +package test + +import kotlin.reflect.KClass +import kotlin.test.* + +class K + +class Test { + fun kClass(): Any = K::class + + fun doTest(k1: KClass<*>, k2: KClass<*>) { + // KClass instances should be equal for classes loaded with the child and the parent + assertEquals(k1, k2) + } +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java index a359d254814..f3eca85f790 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CodegenTestCase.java @@ -23,7 +23,6 @@ import com.intellij.testFramework.UsefulTestCase; import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment; -import org.jetbrains.kotlin.cli.jvm.config.ConfigPackage; import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime; import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils; import org.jetbrains.kotlin.name.FqName; @@ -133,10 +132,9 @@ public abstract class CodegenTestCase extends UsefulTestCase { fail("Double initialization of class loader in same test"); } - ClassFileFactory factory = generateClassesInFile(); - initializedClassLoader = new GeneratedClassLoader(factory, ForTestCompileRuntime.runtimeJarClassLoader(), getClassPathURLs()); + initializedClassLoader = createClassLoader(); - if (!verifyAllFilesWithAsm(factory, initializedClassLoader)) { + if (!verifyAllFilesWithAsm(generateClassesInFile(), initializedClassLoader)) { fail("Verification failed: see exceptions above"); } @@ -144,7 +142,12 @@ public abstract class CodegenTestCase extends UsefulTestCase { } @NotNull - protected URL[] getClassPathURLs() { + protected GeneratedClassLoader createClassLoader() { + return new GeneratedClassLoader(generateClassesInFile(), ForTestCompileRuntime.runtimeJarClassLoader(), getClassPathURLs()); + } + + @NotNull + private URL[] getClassPathURLs() { List urls = Lists.newArrayList(); for (File file : getJvmClasspathRoots(myEnvironment.getConfiguration())) { try { diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt b/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt new file mode 100644 index 00000000000..85c9c225382 --- /dev/null +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ReflectionClassLoaderTest.kt @@ -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 org.jetbrains.kotlin.test.ConfigurationKind + +public class ReflectionClassLoaderTest : CodegenTestCase() { + override fun getPrefix() = "reflection/classLoaders" + + override fun setUp() { + super.setUp() + createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL) + } + + private fun Class<*>.methodByName(name: String) = getDeclaredMethods().single { it.getName() == name } + + fun doTest(cl1: ClassLoader, cl2: ClassLoader) { + val t1 = cl1.loadClass("test.Test") + val t2 = cl2.loadClass("test.Test") + + fun Class<*>.getKClass() = methodByName("kClass")(newInstance()) + + t1.methodByName("doTest")(t1.newInstance(), t1.getKClass(), t2.getKClass()) + } + + fun testSimpleDifferentClassLoaders() { + loadFile(getPrefix() + "/differentClassLoaders.kt") + + doTest( + createClassLoader(), + createClassLoader() + ) + } + + fun testClassLoaderWithNonTrivialEqualsAndHashCode() { + // Check that class loaders do not participate as keys in hash maps (use identity hash maps instead) + + loadFile(getPrefix() + "/differentClassLoaders.kt") + + class BrokenEqualsClassLoader(parent: ClassLoader) : ClassLoader(parent) { + override fun equals(other: Any?) = true + override fun hashCode() = 0 + } + + doTest( + BrokenEqualsClassLoader(createClassLoader()), + BrokenEqualsClassLoader(createClassLoader()) + ) + } + + fun testParentFirst() { + // Check that for a child class loader, a class reference would be the same as for his parent + + loadFile(getPrefix() + "/parentFirst.kt") + + class ChildClassLoader(parent: ClassLoader) : ClassLoader(parent) + + val parent = createClassLoader() + + doTest( + parent, + ChildClassLoader(parent) + ) + } +}