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