Skip to content

Commit 2b8eff5

Browse files
committed
GROOVY-10289
1 parent 246bcd6 commit 2b8eff5

File tree

6 files changed

+680
-3
lines changed

6 files changed

+680
-3
lines changed

base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/basic/InnerClassTests.java

+27
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,33 @@ public void testInnerClass9() {
372372
runConformTest(sources, "xy");
373373
}
374374

375+
@Test // GROOVY-10289
376+
public void testInnerClass10() {
377+
//@formatter:off
378+
String[] sources = {
379+
"Script.groovy",
380+
"class Outer {\n" +
381+
" static class StaticInner {\n" +
382+
" void test() {\n" +
383+
" throw new NonStaticInner()\n" +
384+
" }\n" +
385+
" }\n" +
386+
" class NonStaticInner extends RuntimeException {\n" +
387+
" }\n" +
388+
"}\n" +
389+
"Outer.StaticInner.test()\n",
390+
};
391+
//@formatter:on
392+
393+
runNegativeTest(sources,
394+
"----------\n" +
395+
"1. ERROR in Script.groovy (at line 4)\n" +
396+
"\tthrow new NonStaticInner()\n" +
397+
"\t ^^^^^^^^^^^^^^\n" +
398+
"Groovy:No enclosing instance passed in constructor call of a non-static inner class\n" +
399+
"----------\n");
400+
}
401+
375402
@Test
376403
public void testAnonymousInnerClass1() {
377404
//@formatter:off

base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/InnerClassVisitor.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,15 @@ else if (currentField != null)
271271
isInStaticContext = currentField.isStatic();
272272
else if (processingObjInitStatements)
273273
isInStaticContext = false;
274-
274+
// GRECLIPSE add -- GROOVY-10289
275+
ClassNode enclosing = classNode;
276+
while (!isInStaticContext && !enclosing.equals(cn.getOuterClass())) {
277+
isInStaticContext = (enclosing.getModifiers() & ACC_STATIC) != 0;
278+
// TODO: if enclosing is a local type, also test field or method
279+
enclosing = enclosing.getOuterClass();
280+
if (enclosing == null) break;
281+
}
282+
// GRECLIPSE end
275283
// if constructor call is not in static context, return
276284
if (isInStaticContext) {
277285
// constructor call is in static context and the inner class is non-static - 1st arg is supposed to be

base/org.codehaus.groovy30/.checkstyle

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
<file-match-pattern match-pattern="groovy/ast/expr/(Static)?MethodCallExpression.java" include-pattern="false" />
4040
<file-match-pattern match-pattern="groovy/ast/tools/(Expression|General|Generics)Utils.java" include-pattern="false" />
4141
<file-match-pattern match-pattern="groovy/ast/tools/WideningCategories.java" include-pattern="false" />
42-
<file-match-pattern match-pattern="groovy/classgen/(Annotation|Enum|VariableScope)Visitor.java" include-pattern="false" />
42+
<file-match-pattern match-pattern="groovy/classgen/(Annotation|Enum|InnerClass|VariableScope)Visitor.java" include-pattern="false" />
4343
<file-match-pattern match-pattern="groovy/classgen/AsmClassGenerator.java" include-pattern="false" />
4444
<file-match-pattern match-pattern="groovy/classgen/(Extended)?Verifier.java" include-pattern="false" />
4545
<file-match-pattern match-pattern="groovy/classgen/asm/ClosureWriter.java" include-pattern="false" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
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.classgen;
20+
21+
import org.codehaus.groovy.ast.ClassHelper;
22+
import org.codehaus.groovy.ast.ClassNode;
23+
import org.codehaus.groovy.ast.CodeVisitorSupport;
24+
import org.codehaus.groovy.ast.ConstructorNode;
25+
import org.codehaus.groovy.ast.FieldNode;
26+
import org.codehaus.groovy.ast.GroovyCodeVisitor;
27+
import org.codehaus.groovy.ast.InnerClassNode;
28+
import org.codehaus.groovy.ast.MethodNode;
29+
import org.codehaus.groovy.ast.Parameter;
30+
import org.codehaus.groovy.ast.PropertyNode;
31+
import org.codehaus.groovy.ast.Variable;
32+
import org.codehaus.groovy.ast.VariableScope;
33+
import org.codehaus.groovy.ast.expr.ClosureExpression;
34+
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
35+
import org.codehaus.groovy.ast.expr.Expression;
36+
import org.codehaus.groovy.ast.expr.MethodCallExpression;
37+
import org.codehaus.groovy.ast.expr.TupleExpression;
38+
import org.codehaus.groovy.ast.expr.VariableExpression;
39+
import org.codehaus.groovy.ast.stmt.BlockStatement;
40+
import org.codehaus.groovy.ast.stmt.ExpressionStatement;
41+
import org.codehaus.groovy.control.CompilationUnit;
42+
import org.codehaus.groovy.control.SourceUnit;
43+
import org.codehaus.groovy.transform.trait.Traits;
44+
import groovyjarjarasm.asm.Opcodes;
45+
46+
import java.util.ArrayList;
47+
import java.util.Arrays;
48+
import java.util.Iterator;
49+
import java.util.List;
50+
51+
import static org.codehaus.groovy.ast.tools.GeneralUtils.attrX;
52+
import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
53+
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
54+
import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
55+
56+
public class InnerClassVisitor extends InnerClassVisitorHelper implements Opcodes {
57+
58+
private ClassNode classNode;
59+
private FieldNode currentField;
60+
private MethodNode currentMethod;
61+
private final SourceUnit sourceUnit;
62+
private boolean inClosure, processingObjInitStatements;
63+
64+
public InnerClassVisitor(CompilationUnit cu, SourceUnit su) {
65+
sourceUnit = su;
66+
}
67+
68+
@Override
69+
protected SourceUnit getSourceUnit() {
70+
return sourceUnit;
71+
}
72+
73+
@Override
74+
public void visitClass(ClassNode node) {
75+
classNode = node;
76+
InnerClassNode innerClass = null;
77+
if (!node.isEnum() && !node.isInterface() && node instanceof InnerClassNode) {
78+
innerClass = (InnerClassNode) node;
79+
if (innerClass.getVariableScope() == null && (innerClass.getModifiers() & ACC_STATIC) == 0) {
80+
innerClass.addField("this$0", ACC_FINAL | ACC_SYNTHETIC, node.getOuterClass().getPlainNodeReference(), null);
81+
}
82+
}
83+
84+
super.visitClass(node);
85+
86+
if (node.isEnum() || node.isInterface()) return;
87+
if (innerClass == null) return;
88+
89+
if (node.getSuperClass().isInterface() || Traits.isAnnotatedWithTrait(node.getSuperClass())) {
90+
node.addInterface(node.getUnresolvedSuperClass());
91+
node.setUnresolvedSuperClass(ClassHelper.OBJECT_TYPE);
92+
}
93+
}
94+
95+
@Override
96+
public void visitClosureExpression(ClosureExpression expression) {
97+
boolean inClosureOld = inClosure;
98+
inClosure = true;
99+
super.visitClosureExpression(expression);
100+
inClosure = inClosureOld;
101+
}
102+
103+
@Override
104+
protected void visitObjectInitializerStatements(ClassNode node) {
105+
processingObjInitStatements = true;
106+
super.visitObjectInitializerStatements(node);
107+
processingObjInitStatements = false;
108+
}
109+
110+
@Override
111+
protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
112+
currentMethod = node;
113+
visitAnnotations(node);
114+
visitClassCodeContainer(node.getCode());
115+
// GROOVY-5681: initial expressions should be visited too!
116+
for (Parameter param : node.getParameters()) {
117+
if (param.hasInitialExpression()) {
118+
param.getInitialExpression().visit(this);
119+
}
120+
visitAnnotations(param);
121+
}
122+
currentMethod = null;
123+
}
124+
125+
@Override
126+
public void visitField(FieldNode node) {
127+
currentField = node;
128+
super.visitField(node);
129+
currentField = null;
130+
}
131+
132+
@Override
133+
public void visitProperty(PropertyNode node) {
134+
final FieldNode field = node.getField();
135+
final Expression init = field.getInitialExpression();
136+
field.setInitialValueExpression(null);
137+
super.visitProperty(node);
138+
field.setInitialValueExpression(init);
139+
}
140+
141+
@Override
142+
public void visitConstructorCallExpression(ConstructorCallExpression call) {
143+
super.visitConstructorCallExpression(call);
144+
if (!call.isUsingAnonymousInnerClass()) {
145+
passThisReference(call);
146+
return;
147+
}
148+
149+
InnerClassNode innerClass = (InnerClassNode) call.getType();
150+
ClassNode outerClass = innerClass.getOuterClass();
151+
ClassNode superClass = innerClass.getSuperClass();
152+
if (!superClass.isInterface() && superClass.getOuterClass() != null
153+
&& !(superClass.isStaticClass() || (superClass.getModifiers() & ACC_STATIC) != 0)) {
154+
insertThis0ToSuperCall(call, innerClass);
155+
}
156+
if (!innerClass.getDeclaredConstructors().isEmpty()) return;
157+
if ((innerClass.getModifiers() & ACC_STATIC) != 0) return;
158+
159+
VariableScope scope = innerClass.getVariableScope();
160+
if (scope == null) return;
161+
boolean isStatic = !inClosure && isStatic(innerClass, scope, call);
162+
163+
// expressions = constructor call arguments
164+
List<Expression> expressions = ((TupleExpression) call.getArguments()).getExpressions();
165+
// block = init code for the constructor we produce
166+
BlockStatement block = new BlockStatement();
167+
// parameters = parameters of the constructor
168+
int additionalParamCount = (isStatic ? 0 : 1) + scope.getReferencedLocalVariablesCount();
169+
List<Parameter> parameters = new ArrayList<>(expressions.size() + additionalParamCount);
170+
// superCallArguments = arguments for the super call == the constructor call arguments
171+
List<Expression> superCallArguments = new ArrayList<>(expressions.size());
172+
173+
// first we add a super() call for all expressions given in the constructor call expression
174+
for (int i = 0, n = expressions.size(); i < n; i += 1) {
175+
// add one parameter for each expression in the constructor call
176+
Parameter param = new Parameter(ClassHelper.OBJECT_TYPE, "p" + additionalParamCount + i);
177+
parameters.add(param);
178+
// add the corresponsing argument to the super constructor call
179+
superCallArguments.add(new VariableExpression(param));
180+
}
181+
182+
// add the super call
183+
ConstructorCallExpression cce = new ConstructorCallExpression(
184+
ClassNode.SUPER,
185+
new TupleExpression(superCallArguments)
186+
);
187+
188+
block.addStatement(new ExpressionStatement(cce));
189+
190+
int pCount = 0;
191+
if (!isStatic) {
192+
// need to pass "this" to access unknown methods/properties
193+
ClassNode enclosingType = (inClosure ? ClassHelper.CLOSURE_TYPE : outerClass).getPlainNodeReference();
194+
expressions.add(pCount, new VariableExpression("this", enclosingType));
195+
Parameter thisParameter = new Parameter(enclosingType, "p" + pCount);
196+
parameters.add(pCount++, thisParameter);
197+
198+
// "this" reference is saved in a field named "this$0"
199+
FieldNode thisField = innerClass.addField("this$0", ACC_FINAL | ACC_SYNTHETIC, enclosingType, null);
200+
addFieldInit(thisParameter, thisField, block);
201+
}
202+
203+
// for each shared variable, add a Reference field
204+
for (Iterator<Variable> it = scope.getReferencedLocalVariablesIterator(); it.hasNext();) {
205+
Variable var = it.next();
206+
207+
VariableExpression ve = new VariableExpression(var);
208+
ve.setClosureSharedVariable(true);
209+
ve.setUseReferenceDirectly(true);
210+
expressions.add(pCount, ve);
211+
212+
ClassNode referenceType = ClassHelper.REFERENCE_TYPE.getPlainNodeReference();
213+
Parameter p = new Parameter(referenceType, "p" + pCount);
214+
p.setOriginType(var.getOriginType());
215+
parameters.add(pCount++, p);
216+
217+
VariableExpression initial = new VariableExpression(p);
218+
initial.setSynthetic(true);
219+
initial.setUseReferenceDirectly(true);
220+
FieldNode pField = innerClass.addFieldFirst(ve.getName(), ACC_PUBLIC | ACC_SYNTHETIC, referenceType, initial);
221+
pField.setHolder(true);
222+
pField.setOriginType(ClassHelper.getWrapper(var.getOriginType()));
223+
}
224+
225+
innerClass.addConstructor(ACC_SYNTHETIC, parameters.toArray(Parameter.EMPTY_ARRAY), ClassNode.EMPTY_ARRAY, block);
226+
}
227+
228+
private boolean isStatic(InnerClassNode innerClass, VariableScope scope, ConstructorCallExpression call) {
229+
boolean isStatic = innerClass.isStaticClass();
230+
if (!isStatic) {
231+
if (currentMethod != null) {
232+
if (currentMethod instanceof ConstructorNode) {
233+
boolean[] precedesSuperOrThisCall = new boolean[1];
234+
ConstructorNode ctor = (ConstructorNode) currentMethod;
235+
GroovyCodeVisitor visitor = new CodeVisitorSupport() {
236+
@Override
237+
public void visitConstructorCallExpression(ConstructorCallExpression cce) {
238+
if (cce == call) {
239+
precedesSuperOrThisCall[0] = true;
240+
} else {
241+
super.visitConstructorCallExpression(cce);
242+
}
243+
}
244+
};
245+
if (ctor.firstStatementIsSpecialConstructorCall()) currentMethod.getFirstStatement().visit(visitor);
246+
Arrays.stream(ctor.getParameters()).filter(Parameter::hasInitialExpression).forEach(p -> p.getInitialExpression().visit(visitor));
247+
248+
isStatic = precedesSuperOrThisCall[0];
249+
} else {
250+
isStatic = currentMethod.isStatic();
251+
}
252+
} else if (currentField != null) {
253+
isStatic = currentField.isStatic();
254+
}
255+
}
256+
return isStatic;
257+
}
258+
259+
// this is the counterpart of addThisReference(). To non-static inner classes, outer this should be
260+
// passed as the first argument implicitly.
261+
private void passThisReference(ConstructorCallExpression call) {
262+
ClassNode cn = call.getType().redirect();
263+
if (!shouldHandleImplicitThisForInnerClass(cn)) return;
264+
265+
boolean isInStaticContext = true;
266+
if (currentMethod != null)
267+
isInStaticContext = currentMethod.getVariableScope().isInStaticContext();
268+
else if (currentField != null)
269+
isInStaticContext = currentField.isStatic();
270+
else if (processingObjInitStatements)
271+
isInStaticContext = false;
272+
// GRECLIPSE add -- GROOVY-10289
273+
ClassNode enclosing = classNode;
274+
while (!isInStaticContext && !enclosing.equals(cn.getOuterClass())) {
275+
isInStaticContext = (enclosing.getModifiers() & ACC_STATIC) != 0;
276+
// TODO: if enclosing is a local type, also test field or method
277+
enclosing = enclosing.getOuterClass();
278+
if (enclosing == null) break;
279+
}
280+
// GRECLIPSE end
281+
// if constructor call is not in static context, return
282+
if (isInStaticContext) {
283+
// constructor call is in static context and the inner class is non-static - 1st arg is supposed to be
284+
// passed as enclosing "this" instance
285+
//
286+
Expression args = call.getArguments();
287+
if (args instanceof TupleExpression && ((TupleExpression) args).getExpressions().isEmpty()) {
288+
addError("No enclosing instance passed in constructor call of a non-static inner class", call);
289+
}
290+
return;
291+
}
292+
insertThis0ToSuperCall(call, cn);
293+
}
294+
295+
private void insertThis0ToSuperCall(final ConstructorCallExpression call, final ClassNode cn) {
296+
// calculate outer class which we need for this$0
297+
ClassNode parent = classNode;
298+
int level = 0;
299+
for (; parent != null && parent != cn.getOuterClass(); parent = parent.getOuterClass()) {
300+
level++;
301+
}
302+
303+
// if constructor call is not in outer class, don't pass 'this' implicitly. Return.
304+
if (parent == null) return;
305+
306+
Expression args = call.getArguments();
307+
if (args instanceof TupleExpression) {
308+
Expression this0 = varX("this"); // bypass closure
309+
for (int i = 0; i != level; ++i) {
310+
this0 = attrX(this0, constX("this$0"));
311+
// GROOVY-8104: an anonymous inner class may still have closure nesting
312+
if (i == 0 && classNode.getDeclaredField("this$0").getType().equals(ClassHelper.CLOSURE_TYPE)) {
313+
MethodCallExpression getThis = callX(this0, "getThisObject");
314+
getThis.setImplicitThis(false);
315+
this0 = getThis;
316+
}
317+
}
318+
319+
((TupleExpression) args).getExpressions().add(0, this0);
320+
}
321+
}
322+
}

base/org.codehaus.groovy40/.checkstyle

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
<file-match-pattern match-pattern="groovy/ast/expr/(Static)?MethodCallExpression.java" include-pattern="false" />
3737
<file-match-pattern match-pattern="groovy/ast/tools/(Expression|Generics)Utils.java" include-pattern="false" />
3838
<file-match-pattern match-pattern="groovy/ast/tools/WideningCategories.java" include-pattern="false" />
39-
<file-match-pattern match-pattern="groovy/classgen/(Annotation|Enum|VariableScope)Visitor.java" include-pattern="false" />
39+
<file-match-pattern match-pattern="groovy/classgen/(Annotation|Enum|InnerClass|VariableScope)Visitor.java" include-pattern="false" />
4040
<file-match-pattern match-pattern="groovy/classgen/(Extended)?Verifier.java" include-pattern="false" />
4141
<file-match-pattern match-pattern="groovy/classgen/asm/sc/StaticInvocationWriter.java" include-pattern="false" />
4242
<file-match-pattern match-pattern="groovy/classgen/asm/sc/StaticPropertyAccessHelper.java" include-pattern="false" />

0 commit comments

Comments
 (0)