Skip to content

Commit c8ff8c1

Browse files
committed
GROOVY-11776: set method target to trait helper for static dispatch
4_0_X backport
1 parent 3f2eb94 commit c8ff8c1

File tree

3 files changed

+142
-30
lines changed

3 files changed

+142
-30
lines changed

src/main/java/org/codehaus/groovy/transform/trait/TraitASTTransformation.java

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import static org.objectweb.asm.Opcodes.ACC_FINAL;
8888
import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
8989
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
90+
import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
9091
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
9192
import static org.objectweb.asm.Opcodes.ACC_STATIC;
9293
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
@@ -569,43 +570,55 @@ private void processField(final FieldNode field, final MethodNode initializer, f
569570
}
570571

571572
private MethodNode processMethod(final ClassNode traitClass, final ClassNode traitHelperClass, final MethodNode methodNode, final ClassNode fieldHelper, final Collection<String> knownFields) {
572-
Parameter[] initialParams = methodNode.getParameters();
573-
Parameter[] newParams = new Parameter[initialParams.length + 1];
574-
newParams[0] = createSelfParameter(traitClass, methodNode.isStatic());
575-
System.arraycopy(initialParams, 0, newParams, 1, initialParams.length);
576-
final int mod = methodNode.isPrivate() ? ACC_PRIVATE : ACC_PUBLIC | (methodNode.isFinal() ? ACC_FINAL : 0);
573+
boolean isAbstractMethod = methodNode.isAbstract();
574+
boolean isPrivateMethod = methodNode.isPrivate();
575+
boolean isStaticMethod = methodNode.isStatic();
576+
577+
int modifiers = ACC_PUBLIC;
578+
if (isAbstractMethod) {
579+
modifiers |= ACC_ABSTRACT;
580+
} else {
581+
// public or private
582+
if (isPrivateMethod) {
583+
modifiers ^= ACC_PUBLIC | (!isStaticMethod ? ACC_PRIVATE : ACC_PROTECTED); // GROOVY-11776
584+
}
585+
// static or final (maybe)
586+
if (!isStaticMethod && methodNode.isFinal()) {
587+
modifiers |= ACC_FINAL;
588+
}
589+
modifiers |= ACC_STATIC;
590+
}
591+
592+
Parameter[] methodParams = methodNode.getParameters();
593+
Parameter[] helperParams = new Parameter[methodParams.length + 1];
594+
helperParams[0] = createSelfParameter(traitClass, isStaticMethod);
595+
System.arraycopy(methodParams, 0, helperParams, 1, methodParams.length);
596+
577597
MethodNode mNode = new MethodNode(
578598
methodNode.getName(),
579-
mod | ACC_STATIC,
599+
modifiers,
580600
methodNode.getReturnType(),
581-
newParams,
601+
helperParams,
582602
methodNode.getExceptions(),
583-
processBody(varX(newParams[0]), methodNode.getCode(), traitClass, traitHelperClass, fieldHelper, knownFields)
603+
processBody(varX(helperParams[0]), methodNode.getCode(), traitClass, traitHelperClass, fieldHelper, knownFields)
584604
);
585-
mNode.setSourcePosition(methodNode);
586-
mNode.addAnnotations(filterAnnotations(methodNode.getAnnotations()));
587-
mNode.setGenericsTypes(methodNode.getGenericsTypes());
588-
if (methodNode.isAbstract()) {
589-
mNode.setModifiers(ACC_PUBLIC | ACC_ABSTRACT);
590-
} else {
591-
methodNode.addAnnotation(new AnnotationNode(Traits.IMPLEMENTED_CLASSNODE));
605+
for (AnnotationNode annotation : methodNode.getAnnotations()) {
606+
if (!annotation.getClassNode().equals(OVERRIDE_TYPE)) {
607+
mNode.addAnnotation(annotation);
608+
}
592609
}
593-
methodNode.setCode(null);
610+
mNode.setGenericsTypes(methodNode.getGenericsTypes());
611+
mNode.setSourcePosition(methodNode);
594612

595-
if (!methodNode.isPrivate() && !methodNode.isStatic()) {
613+
if (!isAbstractMethod) {
614+
methodNode.addAnnotation(Traits.IMPLEMENTED_CLASSNODE);
615+
}
616+
if (!isPrivateMethod && !isStaticMethod) {
596617
methodNode.setModifiers(ACC_PUBLIC | ACC_ABSTRACT);
597618
}
598-
return mNode;
599-
}
619+
methodNode.setCode(null);
600620

601-
private static List<AnnotationNode> filterAnnotations(final List<AnnotationNode> annotations) {
602-
List<AnnotationNode> result = new ArrayList<>(annotations.size());
603-
for (AnnotationNode annotation : annotations) {
604-
if (!annotation.getClassNode().equals(OVERRIDE_TYPE)) {
605-
result.add(annotation);
606-
}
607-
}
608-
return result;
621+
return mNode;
609622
}
610623

611624
private static Parameter createSelfParameter(final ClassNode traitClass, boolean isStatic) {

src/main/java/org/codehaus/groovy/transform/trait/TraitComposer.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,20 @@ private static void createForwarderMethod(
325325
helperMethodArgList
326326
);
327327
mce.setImplicitThis(false);
328+
if (!helperMethod.isPrivate()) mce.setMethodTarget(helperMethod); // GROOVY-11776
328329

329330
ClassNode[] exceptionTypes = GenericsUtils.correctToGenericsSpecRecurse(genericsSpec, copyExceptions(helperMethod.getExceptions()));
330331
ClassNode returnType = GenericsUtils.correctToGenericsSpecRecurse(genericsSpec, helperMethod.getReturnType());
331332
boolean castRequired = !genericsSpec.isEmpty() && !helperMethod.isVoidMethod();
332333

333334
boolean isHelperForStaticMethod = ClassHelper.isClassType(helperMethodParams[0].getOriginType());
334335
if (helperMethod.isPrivate() && !isHelperForStaticMethod) {
335-
// GROOVY-7213: do not create forwarder for private methods
336-
return;
336+
return; // GROOVY-7213: don't create forwarder for private method
337+
}
338+
int modifiers = helperMethod.getModifiers() & ~Opcodes.ACC_PROTECTED;
339+
if (!helperMethod.isPublic()) {
340+
modifiers &= ~Opcodes.ACC_PRIVATE;
337341
}
338-
int modifiers = helperMethod.getModifiers();
339342
if (!isHelperForStaticMethod) {
340343
modifiers &= ~Opcodes.ACC_STATIC;
341344
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.codehaus.groovy.transform.traitx
20+
21+
import org.codehaus.groovy.control.CompilationUnit
22+
import org.codehaus.groovy.control.CompilerConfiguration
23+
import org.junit.Test
24+
import org.objectweb.asm.ClassReader
25+
import org.objectweb.asm.util.CheckClassAdapter
26+
27+
final class Groovy11776 {
28+
29+
@Test
30+
void testTraitMethodOverloads() {
31+
File sourceDir = File.createTempDir()
32+
File targetDir = File.createTempDir()
33+
try {
34+
def a = new File(sourceDir, 'A.groovy')
35+
a.write '''
36+
trait A {
37+
def foo(Object o) {
38+
return 'foo(o)'
39+
}
40+
def foo(Map<String,Object> m) {
41+
return 'foo(m)'
42+
}
43+
}
44+
'''
45+
def b = new File(sourceDir, 'B.groovy')
46+
b.write '''
47+
class B implements A {
48+
def bar(Object o) {
49+
return 'bar(o)'
50+
}
51+
def bar(Map<String,Object> m) {
52+
return 'bar(m)'
53+
}
54+
}
55+
'''
56+
def c = new File(sourceDir, 'C.groovy')
57+
c.write '''
58+
new B().with {
59+
assert bar( (Object) null) == 'bar(o)'
60+
assert bar(null as Object) == 'bar(o)'
61+
assert foo( (Object) null) == 'foo(o)'
62+
assert foo(null as Object) == 'foo(o)'
63+
}
64+
(new Object() as A).with {
65+
assert foo( (Object) null) == 'foo(o)'
66+
assert foo(null as Object) == 'foo(o)'
67+
}
68+
'''
69+
70+
def config = new CompilerConfiguration(targetDirectory: targetDir)
71+
def loader = new GroovyClassLoader(this.class.classLoader)
72+
def unit = new CompilationUnit(config, null, loader)
73+
unit.addSources(a, b, c)
74+
unit.compile()
75+
76+
loader.addClasspath(targetDir.absolutePath)
77+
loader.loadClass('C', true).main()
78+
79+
// produce bytecode for class B
80+
def writer = new StringWriter()
81+
def reader = new ClassReader(unit.classes.find{ it.name == 'B' }.bytes)
82+
CheckClassAdapter.verify(reader, loader, true, new PrintWriter(writer))
83+
84+
def string = writer.toString().with {
85+
int start = indexOf('foo(Ljava/lang/Object;)')
86+
int until = indexOf('ARETURN', start) + 8
87+
substring(start, until)
88+
}
89+
assert !string.contains('INVOKEDYNAMIC invoke(Ljava/lang/Class;')
90+
assert string.contains('INVOKESTATIC A$Trait$Helper.foo')
91+
} finally {
92+
sourceDir.deleteDir()
93+
targetDir.deleteDir()
94+
}
95+
}
96+
}

0 commit comments

Comments
 (0)