GROOVY-9873: use class loader of SAM type for proxy creation
diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 480c550..4308fa9 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -2676,11 +2676,13 @@
}
/**
- * <p>Retrieves a property on the given receiver for the specified arguments. The sender is the class that is requesting the property from the object.
- * The MetaClass will attempt to establish the method to invoke based on the name and arguments provided.
- *
- * <p>The useSuper and fromInsideClass help the Groovy runtime perform optimisations on the call to go directly
- * to the super class if necessary
+ * Writes a property on the given receiver for the specified arguments. The
+ * sender is the class that is requesting the property from the object. The
+ * MetaClass will attempt to establish the method to invoke based on the name
+ * and arguments provided.
+ * <p>
+ * The useSuper and fromInsideClass help the runtime perform optimisations
+ * on the call to go directly to the super class if necessary
*
* @param sender The java.lang.Class instance that is mutating the property
* @param object The Object which the property is being set on
@@ -2751,10 +2753,13 @@
method = listeners.get(name);
ambiguousListener = (method == AMBIGUOUS_LISTENER_METHOD);
if (method != null && !ambiguousListener && newValue instanceof Closure) {
+ // bean.name = { -> } is short for bean.addSomeListener({ -> });
+ // where "name" derives from the SomeListener interface's method
+ var listener = method.getParameterTypes()[0].getTheClass();
Object proxy = Proxy.newProxyInstance(
- theClass.getClassLoader(),
- new Class[]{method.getParameterTypes()[0].getTheClass()},
- new ConvertedClosure((Closure) newValue, name));
+ listener.getClassLoader(),
+ new Class[]{listener},
+ new ConvertedClosure((Closure<?>) newValue, name));
arguments = new Object[]{proxy};
newValue = proxy;
} else {
diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java
index 55c77b6..673cc75 100644
--- a/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java
+++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/TypeTransformers.java
@@ -141,10 +141,10 @@
}
/**
- * creates a method handle able to transform the given Closure into a SAM type
- * if the given parameter is a SAM type
+ * Creates a method handle that transforms the given Closure into the given
+ * parameter type, if it is a SAM type.
*/
- private static MethodHandle createSAMTransform(Object arg, Class<?> parameter) {
+ private static MethodHandle createSAMTransform(Object closure, Class<?> parameter) {
Method method = CachedSAMClass.getSAMMethod(parameter);
if (method == null) return null;
// TODO: have to think about how to optimize this!
@@ -164,17 +164,14 @@
}
// the following code will basically do this:
// return Proxy.newProxyInstance(
- // arg.getClass().getClassLoader(),
+ // parameter.getClassLoader(),
// new Class[]{parameter},
- // new ConvertedClosure((Closure) arg));
+ // new ConvertedClosure((Closure)closure, method.getName()));
// TO_REFLECTIVE_PROXY will do that for us, though
// input is the closure, the method name, the class loader and the
- // class[]. All of that but the closure must be provided here
+ // class array. All of that but the closure must be provided here.
MethodHandle ret = TO_REFLECTIVE_PROXY;
- ret = MethodHandles.insertArguments(ret, 1,
- method.getName(),
- arg.getClass().getClassLoader(),
- new Class[]{parameter});
+ ret = MethodHandles.insertArguments(ret, 1, method.getName(), parameter.getClassLoader(), new Class[]{parameter});
return ret;
} else {
// the following code will basically do this:
@@ -207,8 +204,8 @@
}
/**
- * returns a transformer later applied as filter to transform one
- * number into another
+ * Returns a transformer later applied as filter to transform one
+ * number into another.
*/
private static MethodHandle selectNumberTransformer(Class<?> param, Object arg) {
param = TypeHelper.getWrapperClass(param);
diff --git a/src/test/groovy/bugs/Groovy9873.groovy b/src/test/groovy/bugs/Groovy9873.groovy
new file mode 100644
index 0000000..65afad4
--- /dev/null
+++ b/src/test/groovy/bugs/Groovy9873.groovy
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 groovy.bugs
+
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.codehaus.groovy.tools.javac.JavaAwareCompilationUnit
+import org.junit.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+final class Groovy9873 {
+
+ @Test
+ void testCoerceClosure1() {
+ assertScript '''
+ @Grab('io.vavr:vavr:0.10.4;transitive=false')
+ import io.vavr.control.Try
+ class C { }
+ C resolve() {new C()}
+ Try.of(this::resolve)
+ '''
+ }
+
+ @Test
+ void testCoerceClosure2() {
+ def config = new CompilerConfiguration().tap {
+ jointCompilationOptions = [memStub: true]
+ targetDirectory = File.createTempDir()
+ }
+ File parentDir = File.createTempDir()
+ try {
+ def c = new File(parentDir, 'C.groovy')
+ c.write '''
+ class C<T> {
+ private T t
+ C(T item) {
+ t = item
+ }
+ static <U> C<U> of(U item) {
+ new C<U>(item)
+ }
+ def <V> C<V> map(F<? super T, ? super V> func) {
+ new C<V>(func.apply(t))
+ }
+ }
+ '''
+ def d = new File(parentDir, 'D.groovy')
+ d.write '''
+ class D {
+ static <W> Set<W> wrap(W o) {
+ Collections.singleton(o)
+ }
+ }
+ '''
+ def f = new File(parentDir, 'F.groovy')
+ f.write '''
+ interface F<X,Y> {
+ Y apply(X x)
+ }
+ '''
+ def g = new File(parentDir, 'G.groovy')
+ g.write '''
+ def c = C.of(123)
+ def d = c.map(D.&wrap)
+ def e = d.map(x -> x.first().intValue())
+ '''
+
+ def loader = new GroovyClassLoader(this.class.classLoader)
+ def cu = new JavaAwareCompilationUnit(config, loader)
+ cu.addSources(c, d, f, g)
+ cu.compile()
+
+ loader.loadClass('G').main()
+ } finally {
+ parentDir.deleteDir()
+ config.targetDirectory.deleteDir()
+ }
+ }
+
+ @Test
+ void testCoerceClosure3() {
+ def config = new CompilerConfiguration().tap {
+ jointCompilationOptions = [memStub: true]
+ targetDirectory = File.createTempDir()
+ }
+ File parentDir = File.createTempDir()
+ try {
+ def f = new File(parentDir, 'F.groovy')
+ f.write '''
+ class FInfo extends EventObject {
+ FInfo() { super(null) }
+ }
+ interface FListener extends EventListener {
+ void somethingHappened(FInfo i)
+ }
+ '''
+ def g = new File(parentDir, 'G.groovy')
+ g.write '''
+ class H {
+ void addFListener(FListener f) {
+ f.somethingHappened(null)
+ }
+ void removeFListener(FListener f) {
+ }
+ }
+
+ new H().somethingHappened = { info -> }
+ '''
+
+ def loader = new GroovyClassLoader(this.class.classLoader)
+ def cu = new JavaAwareCompilationUnit(config, loader)
+ cu.addSources(f, g)
+ cu.compile()
+
+ loader.loadClass('G').main()
+ } finally {
+ parentDir.deleteDir()
+ config.targetDirectory.deleteDir()
+ }
+ }
+}
diff --git a/src/test/groovy/transform/stc/GenericsSTCTest.groovy b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
index faf5e88..2d41d77 100644
--- a/src/test/groovy/transform/stc/GenericsSTCTest.groovy
+++ b/src/test/groovy/transform/stc/GenericsSTCTest.groovy
@@ -2653,13 +2653,9 @@
}
}
- void test() {
- def c = C.of(42)
- def d = c.map($toSet)
- def e = d.map(x -> x.first().intValue())
- }
-
- test()
+ def c = C.of(42)
+ def d = c.map($toSet)
+ def e = d.map(x -> x.first().intValue())
"""
}
}