Skip to content

Commit ad56669

Browse files
committed
GROOVY-7970: Can't call private method from outer class when using anonymous inner classes and @cs (closes groovy#452)
1 parent 6f39e27 commit ad56669

File tree

3 files changed

+184
-10
lines changed

3 files changed

+184
-10
lines changed

src/main/org/codehaus/groovy/classgen/asm/sc/StaticInvocationWriter.java

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.codehaus.groovy.ast.ClassHelper;
2323
import org.codehaus.groovy.ast.ClassNode;
2424
import org.codehaus.groovy.ast.ConstructorNode;
25+
import org.codehaus.groovy.ast.FieldNode;
2526
import org.codehaus.groovy.ast.GroovyCodeVisitor;
2627
import org.codehaus.groovy.ast.InnerClassNode;
2728
import org.codehaus.groovy.ast.MethodNode;
@@ -186,7 +187,16 @@ public void writeSpecialConstructorCall(final ConstructorCallExpression call) {
186187
/**
187188
* Attempts to make a direct method call on a bridge method, if it exists.
188189
*/
190+
@Deprecated
189191
protected boolean tryBridgeMethod(MethodNode target, Expression receiver, boolean implicitThis, TupleExpression args) {
192+
return tryBridgeMethod(target, receiver, implicitThis, args, null);
193+
}
194+
195+
/**
196+
* Attempts to make a direct method call on a bridge method, if it exists.
197+
*/
198+
protected boolean tryBridgeMethod(MethodNode target, Expression receiver, boolean implicitThis,
199+
TupleExpression args, ClassNode thisClass) {
190200
ClassNode lookupClassNode;
191201
if (target.isProtected()) {
192202
lookupClassNode = controller.getClassNode();
@@ -203,8 +213,22 @@ protected boolean tryBridgeMethod(MethodNode target, Expression receiver, boolea
203213
MethodNode bridge = bridges==null?null:bridges.get(target);
204214
if (bridge != null) {
205215
Expression fixedReceiver = receiver;
206-
if (implicitThis && !controller.isInClosure()) {
207-
fixedReceiver = new PropertyExpression(new ClassExpression(lookupClassNode), "this");
216+
if (implicitThis) {
217+
if (!controller.isInClosure()) {
218+
fixedReceiver = new PropertyExpression(new ClassExpression(lookupClassNode), "this");
219+
} else if (thisClass != null) {
220+
ClassNode current = thisClass.getOuterClass();
221+
fixedReceiver = new VariableExpression("thisObject", current);
222+
// adjust for multiple levels of nesting if needed
223+
while (current != null && current instanceof InnerClassNode && !lookupClassNode.equals(current)) {
224+
FieldNode thisField = current.getField("this$0");
225+
current = current.getOuterClass();
226+
if (thisField != null) {
227+
fixedReceiver = new PropertyExpression(fixedReceiver, "this$0");
228+
fixedReceiver.setType(current);
229+
}
230+
}
231+
}
208232
}
209233
ArgumentListExpression newArgs = new ArgumentListExpression(target.isStatic()?new ConstantExpression(null):fixedReceiver);
210234
for (Expression expression : args.getExpressions()) {
@@ -261,7 +285,7 @@ protected boolean writeDirectMethodCall(final MethodNode target, final boolean i
261285
&& controller.isInClosure()
262286
&& !target.isPublic()
263287
&& target.getDeclaringClass() != classNode) {
264-
if (!tryBridgeMethod(target, receiver, implicitThis, args)) {
288+
if (!tryBridgeMethod(target, receiver, implicitThis, args, classNode)) {
265289
// replace call with an invoker helper call
266290
ArrayExpression arr = new ArrayExpression(ClassHelper.OBJECT_TYPE, args.getExpressions());
267291
MethodCallExpression mce = new MethodCallExpression(
@@ -281,6 +305,8 @@ protected boolean writeDirectMethodCall(final MethodNode target, final boolean i
281305
}
282306
return true;
283307
}
308+
Expression fixedReceiver = null;
309+
boolean fixedImplicitThis = implicitThis;
284310
if (target.isPrivate()) {
285311
if (tryPrivateMethod(target, implicitThis, receiver, args, classNode)) return true;
286312
} else if (target.isProtected()) {
@@ -295,16 +321,36 @@ protected boolean writeDirectMethodCall(final MethodNode target, final boolean i
295321
controller.getSourceUnit().addError(
296322
new SyntaxException("Method " + target.getName() + " is protected in " + target.getDeclaringClass().toString(false),
297323
src.getLineNumber(), src.getColumnNumber(), src.getLastLineNumber(), src.getLastColumnNumber()));
298-
} else if (!node.isDerivedFrom(target.getDeclaringClass()) && tryBridgeMethod(target, receiver, implicitThis, args)) {
324+
} else if (!node.isDerivedFrom(target.getDeclaringClass()) && tryBridgeMethod(target, receiver, implicitThis, args, classNode)) {
299325
return true;
300326
}
327+
} else if (target.isPublic() && receiver != null) {
328+
if (implicitThis
329+
&& !classNode.isDerivedFrom(target.getDeclaringClass())
330+
&& !classNode.implementsInterface(target.getDeclaringClass())
331+
&& classNode instanceof InnerClassNode && controller.isInClosure()) {
332+
ClassNode current = classNode.getOuterClass();
333+
fixedReceiver = new VariableExpression("thisObject", current);
334+
// adjust for multiple levels of nesting if needed
335+
while (current != null && current instanceof InnerClassNode && !classNode.equals(current)) {
336+
FieldNode thisField = current.getField("this$0");
337+
current = current.getOuterClass();
338+
if (thisField != null) {
339+
fixedReceiver = new PropertyExpression(fixedReceiver, "this$0");
340+
fixedReceiver.setType(current);
341+
fixedImplicitThis = false;
342+
}
343+
}
344+
}
301345
}
302346
if (receiver != null) {
303-
if (!(receiver instanceof VariableExpression) || !((VariableExpression) receiver).isSuperExpression()) {
347+
boolean callToSuper = receiver instanceof VariableExpression && ((VariableExpression) receiver).isSuperExpression();
348+
if (!callToSuper) {
349+
fixedReceiver = fixedReceiver == null ? receiver : fixedReceiver;
304350
// in order to avoid calls to castToType, which is the dynamic behaviour, we make sure that we call CHECKCAST instead
305351
// then replace the top operand type
306-
Expression checkCastReceiver = new CheckcastReceiverExpression(receiver, target);
307-
return super.writeDirectMethodCall(target, implicitThis, checkCastReceiver, args);
352+
Expression checkCastReceiver = new CheckcastReceiverExpression(fixedReceiver, target);
353+
return super.writeDirectMethodCall(target, fixedImplicitThis, checkCastReceiver, args);
308354
}
309355
}
310356
return super.writeDirectMethodCall(target, implicitThis, receiver, args);
@@ -316,7 +362,7 @@ private boolean tryPrivateMethod(final MethodNode target, final boolean implicit
316362
if ((isPrivateBridgeMethodsCallAllowed(declaringClass, classNode) || isPrivateBridgeMethodsCallAllowed(classNode, declaringClass))
317363
&& declaringClass.getNodeMetaData(PRIVATE_BRIDGE_METHODS) != null
318364
&& !declaringClass.equals(classNode)) {
319-
if (tryBridgeMethod(target, receiver, implicitThis, args)) {
365+
if (tryBridgeMethod(target, receiver, implicitThis, args, classNode)) {
320366
return true;
321367
} else if (declaringClass != classNode) {
322368
controller.getSourceUnit().addError(new SyntaxException("Cannot call private method " + (target.isStatic() ? "static " : "") +

src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3849,8 +3849,11 @@ protected List<MethodNode> findMethod(
38493849
collectAllInterfaceMethodsByName(receiver, name, methods);
38503850
methods.addAll(OBJECT_TYPE.getMethods(name));
38513851
}
3852-
if (typeCheckingContext.getEnclosingClosure() == null) {
3853-
// not in a closure
3852+
// TODO: investigate the trait exclusion a bit further, needed otherwise
3853+
// CallMethodOfTraitInsideClosureAndClosureParamTypeInference fails saying
3854+
// not static method can't be called from a static context
3855+
if (typeCheckingContext.getEnclosingClosure() == null || (receiver instanceof InnerClassNode && !receiver.getName().endsWith("$Trait$Helper"))) {
3856+
// not in a closure or within an inner class
38543857
ClassNode parent = receiver;
38553858
while (parent instanceof InnerClassNode && !parent.isStaticClass()) {
38563859
parent = parent.getOuterClass();
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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 groovy.bugs
20+
21+
class Groovy7970Bug extends GroovyTestCase {
22+
23+
private static final String getScriptAIC(String visibility, boolean cs) {
24+
"""
25+
${ cs ? '@groovy.transform.CompileStatic' : ''}
26+
class Bar {
27+
$visibility String renderTemplate(String arg) { "dummy\$arg" }
28+
def foo() {
29+
new Object() {
30+
String bar() {
31+
'A'.with{ renderTemplate(it) }
32+
}
33+
}
34+
}
35+
}
36+
assert new Bar().foo().bar() == 'dummyA'
37+
"""
38+
}
39+
40+
private static final String getScriptNestedAIC(String visibility, boolean cs) {
41+
"""
42+
${ cs ? '@groovy.transform.CompileStatic' : ''}
43+
class Bar {
44+
$visibility String renderTemplate(String arg) { "dummy\$arg" }
45+
def foo() {
46+
new Object() {
47+
def bar() {
48+
new Object() { String xxx() { 'B'.with{ renderTemplate(it) } }}
49+
}
50+
}
51+
}
52+
}
53+
assert new Bar().foo().bar().xxx() == 'dummyB'
54+
"""
55+
}
56+
57+
private static final String getScriptInner(String visibility, boolean cs) {
58+
"""
59+
${ cs ? '@groovy.transform.CompileStatic' : ''}
60+
class Bar {
61+
$visibility String renderTemplate(String arg) { "dummy\$arg" }
62+
class Inner {
63+
String baz() {
64+
'C'.with{ renderTemplate(it) }
65+
}
66+
}
67+
}
68+
def b = new Bar()
69+
assert new Bar.Inner(b).baz() == 'dummyC'
70+
"""
71+
}
72+
73+
private static final String getScriptNestedInner(String visibility, boolean cs) {
74+
"""
75+
${ cs ? '@groovy.transform.CompileStatic' : ''}
76+
class Bar {
77+
$visibility String renderTemplate(String arg) { "dummy\$arg" }
78+
class Inner {
79+
class InnerInner {
80+
String xxx() { 'D'.with{ renderTemplate(it) } }
81+
}
82+
}
83+
}
84+
def b = new Bar()
85+
def bi = new Bar.Inner(b)
86+
assert new Bar.Inner.InnerInner(bi).xxx() == 'dummyD'
87+
"""
88+
}
89+
90+
void testMethodAccessFromClosureWithinInnerClass() {
91+
assertScript getScriptInner('public', false)
92+
assertScript getScriptInner('protected', false)
93+
assertScript getScriptInner('private', false)
94+
assertScript getScriptNestedInner('public', false)
95+
assertScript getScriptNestedInner('protected', false)
96+
assertScript getScriptNestedInner('private', false)
97+
}
98+
99+
void testMethodAccessFromClosureWithinInnerClass_CS() {
100+
assertScript getScriptInner('public', true)
101+
assertScript getScriptInner('protected', true)
102+
assertScript getScriptInner('private', true)
103+
assertScript getScriptNestedInner('public', true)
104+
assertScript getScriptNestedInner('protected', true)
105+
assertScript getScriptNestedInner('private', true)
106+
}
107+
108+
void testMethodAccessFromClosureWithinAIC() {
109+
assertScript getScriptAIC('public', false)
110+
assertScript getScriptAIC('protected', false)
111+
assertScript getScriptAIC('private', false)
112+
assertScript getScriptNestedAIC('public', false)
113+
assertScript getScriptNestedAIC('protected', false)
114+
assertScript getScriptNestedAIC('private', false)
115+
}
116+
117+
void testMethodAccessFromClosureWithinAIC_CS() {
118+
assertScript getScriptAIC('public', true)
119+
assertScript getScriptAIC('protected', true)
120+
assertScript getScriptAIC('private', true)
121+
assertScript getScriptNestedAIC('public', true)
122+
assertScript getScriptNestedAIC('protected', true)
123+
assertScript getScriptNestedAIC('private', true)
124+
}
125+
}

0 commit comments

Comments
 (0)