diff --git a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java
index e557092f69..0a59f13b99 100644
--- a/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java
+++ b/base-test/org.eclipse.jdt.groovy.core.tests.compiler/src/org/eclipse/jdt/groovy/core/tests/xform/StaticCompilationTests.java
@@ -1280,6 +1280,30 @@ public void testCompileStatic7363() {
runConformTest(sources, "42");
}
+ @Test
+ public void testCompileStatic7526() {
+ //@formatter:off
+ String[] sources = {
+ "Main.groovy",
+ "boolean check(String s) { true }\n" +
+ "@groovy.transform.CompileStatic\n" +
+ "void test(Pogo pogo) {\n" +
+ " if (check(pogo?.field)) {\n" + // VerifyError: Bad type on operand stack
+ " print 'works'\n" +
+ " }\n" +
+ "}\n" +
+ "test(new Pogo())\n",
+
+ "Pogo.groovy",
+ "class Pogo {\n" +
+ " public String field\n" +
+ "}\n",
+ };
+ //@formatter:on
+
+ runConformTest(sources, "works");
+ }
+
@Test
public void testCompileStatic7549() {
//@formatter:off
@@ -5136,42 +5160,24 @@ public void testCompileStatic9555() {
@Test
public void testCompileStatic9558() {
- //@formatter:off
- String[] sources = {
- "Main.groovy",
- "@groovy.transform.CompileStatic\n" +
- "void test() {\n" +
- " def config = new org.codehaus.groovy.control.CompilerConfiguration()\n" +
- " config.tap {\n" +
- " optimizationOptions['indy'] = true\n" + // Cannot cast object '...' with class 'CompilerConfiguration' to class 'Map'
- " optimizationOptions.indy = true\n" +
- " }\n" +
- "}\n" +
- "test()\n",
- };
- //@formatter:on
-
- runConformTest(sources, "");
- }
-
- @Test
- public void testCompileStatic9558a() {
- //@formatter:off
- String[] sources = {
- "Main.groovy",
- "@groovy.transform.CompileStatic\n" +
- "void test() {\n" +
- " def config = new org.codehaus.groovy.control.CompilerConfiguration()\n" +
- " config.with {\n" +
- " optimizationOptions['indy'] = true\n" + // Cannot cast object '...' with class 'CompilerConfiguration' to class 'Map'
- " optimizationOptions.indy = true\n" +
- " }\n" +
- "}\n" +
- "test()\n",
- };
- //@formatter:on
+ for (String dgm : new String[] {"tap", "with"}) {
+ //@formatter:off
+ String[] sources = {
+ "Main.groovy",
+ "@groovy.transform.CompileStatic\n" +
+ "void test() {\n" +
+ " def config = new org.codehaus.groovy.control.CompilerConfiguration()\n" +
+ " config." + dgm + " {\n" +
+ " optimizationOptions['indy'] = true\n" + // Cannot cast object '...' with class 'CompilerConfiguration' to class 'Map'
+ " optimizationOptions.indy = true\n" +
+ " }\n" +
+ "}\n" +
+ "test()\n",
+ };
+ //@formatter:on
- runConformTest(sources, "");
+ runConformTest(sources);
+ }
}
@Test
@@ -5272,7 +5278,7 @@ public void testCompileStatic9603() {
};
//@formatter:on
- runConformTest(sources, "");
+ runConformTest(sources);
}
@Test
@@ -6348,7 +6354,7 @@ public void testCompileStatic10089() {
@Test
public void testCompileStatic10197() {
- for (String override : new String[]{"int getBaz() {1}", "final int baz = 1"}) {
+ for (String override : new String[] {"int getBaz() {1}", "final int baz = 1"}) {
//@formatter:off
String[] sources = {
"Main.groovy",
diff --git a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
index ced550bd08..e188c542a1 100644
--- a/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
+++ b/base/org.codehaus.groovy25/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
@@ -819,7 +819,7 @@ private void writeNumberNumberCall(final Expression receiver, final String messa
@Override
public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expression objectExpression, String name, MethodCallerMultiAdapter adapter) {
- /* GRECLIPSE edit -- GROOVY-7304, GROOVY-9892
+ /* GRECLIPSE edit -- GROOVY-6954, GROOVY-7304, GROOVY-9892
if (name!=null &&
(adapter == AsmClassGenerator.setField || adapter == AsmClassGenerator.setGroovyObjectField)
) {
@@ -833,12 +833,17 @@ public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expre
}
}
*/
- if (name != null && controller.getCompileStack().isLHS()) {
- ClassNode classNode = controller.getClassNode();
- ClassNode receiverType = getPropertyOwnerType(objectExpression);
+ CompileStack compileStack = controller.getCompileStack();
+ OperandStack operandStack = controller.getOperandStack();
+
+ if (name != null && compileStack.isLHS()) {
+ boolean[] isClassReceiver = new boolean[1];
+ ClassNode receiverType = getPropertyOwnerType(objectExpression, isClassReceiver);
if (adapter == AsmClassGenerator.setField || adapter == AsmClassGenerator.setGroovyObjectField) {
if (setField(expression, objectExpression, receiverType, name)) return;
- } else if (isThisExpression(objectExpression)) {
+ }
+ if (isThisExpression(objectExpression)) {
+ ClassNode classNode = controller.getClassNode();
FieldNode fieldNode = receiverType.getField(name);
if (fieldNode != null && fieldNode.isPrivate() && !receiverType.equals(classNode)
&& StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)) {
@@ -846,9 +851,9 @@ public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expre
if (mutators != null) {
MethodNode methodNode = mutators.get(name);
if (methodNode != null) {
- ClassNode rhsType = controller.getOperandStack().getTopOperand();
- int i = controller.getCompileStack().defineTemporaryVariable("$rhsValue", rhsType, true);
- VariableSlotLoader rhsValue = new VariableSlotLoader(rhsType, i, controller.getOperandStack());
+ ClassNode rhsType = operandStack.getTopOperand();
+ int i = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
+ VariableSlotLoader rhsValue = new VariableSlotLoader(rhsType, i, operandStack);
MethodCallExpression call = new MethodCallExpression(objectExpression, methodNode.getName(), new ArgumentListExpression(fieldNode.isStatic() ? new ConstantExpression(null) : objectExpression, rhsValue));
call.setImplicitThis(expression.isImplicitThis());
@@ -857,13 +862,45 @@ public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expre
call.setMethodTarget(methodNode);
call.visit(controller.getAcg());
- controller.getCompileStack().removeVar(i);
- controller.getOperandStack().pop();
+ compileStack.removeVar(i);
+ operandStack.pop();
return;
}
}
}
}
+ if (isOrImplements(receiverType, MAP_TYPE) && !isClassReceiver[0]) {
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ // store value in temporary variable
+ ClassNode rhsType = operandStack.getTopOperand();
+ int rhs = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
+
+ // push receiver on stack
+ compileStack.pushLHS(false);
+ objectExpression.visit(controller.getAcg());
+ compileStack.popLHS();
+
+ // check if receiver null
+ Label skip = new Label();
+ if (expression.isSafe()) {
+ mv.visitInsn(DUP);
+ mv.visitJumpInsn(IFNULL, skip);
+ }
+
+ mv.visitLdcInsn(name);
+ BytecodeHelper.load(mv, rhsType, rhs);
+ if (isPrimitiveType(rhsType)) BytecodeHelper.doCastToWrappedType(mv, rhsType, getWrapper(rhsType));
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
+
+ if (expression.isSafe()) {
+ mv.visitLabel(skip);
+ }
+ compileStack.removeVar(rhs);
+ operandStack.pop();
+ return;
+ }
+
}
// GRECLIPSE end
super.fallbackAttributeOrPropertySite(expression, objectExpression, name, adapter);
diff --git a/base/org.codehaus.groovy30/.checkstyle b/base/org.codehaus.groovy30/.checkstyle
index bb7277f028..678d5a0dd1 100644
--- a/base/org.codehaus.groovy30/.checkstyle
+++ b/base/org.codehaus.groovy30/.checkstyle
@@ -47,7 +47,7 @@
-
+
diff --git a/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
new file mode 100644
index 0000000000..bf30cb6e9b
--- /dev/null
+++ b/base/org.codehaus.groovy30/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
@@ -0,0 +1,931 @@
+/*
+ * 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 org.codehaus.groovy.classgen.asm.sc;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.classgen.AsmClassGenerator;
+import org.codehaus.groovy.classgen.BytecodeExpression;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.codehaus.groovy.classgen.asm.CallSiteWriter;
+import org.codehaus.groovy.classgen.asm.CompileStack;
+import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
+import org.codehaus.groovy.classgen.asm.OperandStack;
+import org.codehaus.groovy.classgen.asm.TypeChooser;
+import org.codehaus.groovy.classgen.asm.VariableSlotLoader;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
+import org.codehaus.groovy.transform.stc.StaticTypesMarker;
+import groovyjarjarasm.asm.Label;
+import groovyjarjarasm.asm.MethodVisitor;
+import groovyjarjarasm.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.groovy.ast.tools.ClassNodeUtils.getField;
+import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression;
+import static org.apache.groovy.util.BeanUtils.capitalize;
+import static org.codehaus.groovy.ast.ClassHelper.BigDecimal_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.BigInteger_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Boolean_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type;
+import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Integer_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Iterator_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Long_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.boolean_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
+import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
+import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.isGeneratedFunction;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType;
+
+/**
+ * A call site writer which replaces call site caching with static calls. This means that the generated code
+ * looks more like Java code than dynamic Groovy code. Best effort is made to use JVM instructions instead of
+ * calls to helper methods.
+ */
+public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes {
+
+ private static final ClassNode COLLECTION_TYPE = ClassHelper.make(Collection.class);
+ private static final ClassNode INVOKERHELPER_TYPE = ClassHelper.make(InvokerHelper.class);
+ private static final MethodNode COLLECTION_SIZE_METHOD = COLLECTION_TYPE.getMethod("size", Parameter.EMPTY_ARRAY);
+ private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", Parameter.EMPTY_ARRAY);
+ private static final MethodNode MAP_GET_METHOD = MAP_TYPE.getMethod("get", new Parameter[]{new Parameter(OBJECT_TYPE, "key")});
+ private static final MethodNode GROOVYOBJECT_GETPROPERTY_METHOD = GROOVY_OBJECT_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(STRING_TYPE, "propertyName")});
+ private static final MethodNode INVOKERHELPER_GETPROPERTY_METHOD = INVOKERHELPER_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")});
+ private static final MethodNode INVOKERHELPER_GETPROPERTYSAFE_METHOD = INVOKERHELPER_TYPE.getMethod("getPropertySafe", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")});
+
+ private final StaticTypesWriterController controller;
+
+ public StaticTypesCallSiteWriter(final StaticTypesWriterController controller) {
+ super(controller);
+ this.controller = controller;
+ }
+
+ @Override
+ public void generateCallSiteArray() {
+ CallSiteWriter regularCallSiteWriter = controller.getRegularCallSiteWriter();
+ if (regularCallSiteWriter.hasCallSiteUse()) {
+ regularCallSiteWriter.generateCallSiteArray();
+ }
+ }
+
+ @Override
+ public void makeCallSite(final Expression receiver, final String message, final Expression arguments, final boolean safe, final boolean implicitThis, final boolean callCurrent, final boolean callStatic) {
+ }
+
+ @Override
+ public void makeGetPropertySite(final Expression receiver, final String propertyName, final boolean safe, final boolean implicitThis) {
+ Object dynamic = receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY);
+ if (dynamic != null) {
+ makeDynamicGetProperty(receiver, propertyName, safe);
+ return;
+ }
+ boolean[] isClassReceiver = new boolean[1];
+ ClassNode receiverType = getPropertyOwnerType(receiver, isClassReceiver);
+ if (receiverType.isArray() && "length".equals(propertyName)) {
+ receiver.visit(controller.getAcg());
+ ClassNode arrayGetReturnType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
+ controller.getOperandStack().doGroovyCast(arrayGetReturnType);
+ controller.getMethodVisitor().visitInsn(ARRAYLENGTH);
+ controller.getOperandStack().replace(int_TYPE);
+ return;
+ } else if (isOrImplements(receiverType, COLLECTION_TYPE) && ("size".equals(propertyName) || "length".equals(propertyName))) {
+ MethodCallExpression expr = callX(receiver, "size");
+ expr.setMethodTarget(COLLECTION_SIZE_METHOD);
+ expr.setImplicitThis(implicitThis);
+ expr.setSafe(safe);
+ expr.visit(controller.getAcg());
+ return;
+ }
+
+ boolean isStaticProperty = receiver instanceof ClassExpression
+ && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType()));
+
+ if (!isStaticProperty && isOrImplements(receiverType, MAP_TYPE)) {
+ // for maps, replace map.foo with map.get('foo')
+ writeMapDotProperty(receiver, propertyName, safe);
+ return;
+ }
+ if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (receiver instanceof ClassExpression) {
+ if (makeGetField(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ if (makeGetPropertyWithGetter(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ }
+ if (isClassReceiver[0]) {
+ // we are probably looking for a property of the class
+ if (makeGetPropertyWithGetter(receiver, CLASS_Type, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, CLASS_Type, propertyName, safe, false)) return;
+ }
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, propertyName, safe, implicitThis)) return;
+
+ // GROOVY-5580: it is still possible that we're calling a superinterface property
+ String getterName = "get" + capitalize(propertyName);
+ String altGetterName = "is" + capitalize(propertyName);
+ if (receiverType.isInterface()) {
+ MethodNode getterMethod = null;
+ for (ClassNode anInterface : receiverType.getAllInterfaces()) {
+ getterMethod = anInterface.getGetterMethod(getterName);
+ if (getterMethod == null) getterMethod = anInterface.getGetterMethod(altGetterName);
+ if (getterMethod != null) break;
+ }
+ // GROOVY-5585
+ if (getterMethod == null) {
+ getterMethod = OBJECT_TYPE.getGetterMethod(getterName);
+ }
+ if (getterMethod != null) {
+ MethodCallExpression call = callX(receiver, getterName);
+ call.setImplicitThis(false);
+ call.setMethodTarget(getterMethod);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return;
+ }
+ }
+
+ // GROOVY-5568: we would be facing a DGM call, but instead of foo.getText(), have foo.text
+ List methods = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, getterName, ClassNode.EMPTY_ARRAY);
+ for (MethodNode dgm : findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, altGetterName, ClassNode.EMPTY_ARRAY)) {
+ if (Boolean_TYPE.equals(getWrapper(dgm.getReturnType()))) {
+ methods.add(dgm);
+ }
+ }
+ if (!methods.isEmpty()) {
+ List methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY);
+ if (methodNodes.size() == 1) {
+ MethodNode getter = methodNodes.get(0);
+ MethodCallExpression call = callX(receiver, getter.getName());
+ call.setImplicitThis(false);
+ call.setMethodTarget(getter);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return;
+ }
+ }
+
+ if (!isStaticProperty && isOrImplements(receiverType, LIST_TYPE)) {
+ writeListDotProperty(receiver, propertyName, safe);
+ return;
+ }
+
+ addPropertyAccessError(receiver, propertyName, receiverType);
+ controller.getMethodVisitor().visitInsn(ACONST_NULL);
+ controller.getOperandStack().push(OBJECT_TYPE);
+ }
+
+ private void makeDynamicGetProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ MethodNode target = safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD;
+ MethodCallExpression call = callX(
+ classX(INVOKERHELPER_TYPE),
+ target.getName(),
+ args(receiver, constX(propertyName))
+ );
+ call.setImplicitThis(false);
+ call.setMethodTarget(target);
+ call.setSafe(false);
+ call.visit(controller.getAcg());
+ }
+
+ private void writeMapDotProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ receiver.visit(controller.getAcg()); // load receiver
+
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ Label exit = new Label();
+ if (safe) {
+ Label doGet = new Label();
+ mv.visitJumpInsn(IFNONNULL, doGet);
+ controller.getOperandStack().remove(1);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitJumpInsn(GOTO, exit);
+ mv.visitLabel(doGet);
+ receiver.visit(controller.getAcg());
+ }
+
+ mv.visitLdcInsn(propertyName); // load property name
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
+ if (safe) {
+ mv.visitLabel(exit);
+ }
+ controller.getOperandStack().replace(OBJECT_TYPE);
+ }
+
+ private void writeListDotProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ ClassNode componentType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE);
+ if (componentType == null) {
+ componentType = OBJECT_TYPE;
+ }
+ // for lists, replace list.foo with:
+ // def result = new ArrayList(list.size())
+ // for (e in list) { result.add (e.foo) }
+ // result
+ CompileStack compileStack = controller.getCompileStack();
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ Label exit = new Label();
+ if (safe) {
+ receiver.visit(controller.getAcg());
+ Label doGet = new Label();
+ mv.visitJumpInsn(IFNONNULL, doGet);
+ controller.getOperandStack().remove(1);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitJumpInsn(GOTO, exit);
+ mv.visitLabel(doGet);
+ }
+
+ Variable tmpList = varX("tmpList", ClassHelper.make(ArrayList.class));
+ int var = compileStack.defineTemporaryVariable(tmpList, false);
+ Variable iterator = varX("iterator", Iterator_TYPE);
+ int it = compileStack.defineTemporaryVariable(iterator, false);
+ Variable nextVar = varX("next", componentType);
+ final int next = compileStack.defineTemporaryVariable(nextVar, false);
+
+ mv.visitTypeInsn(NEW, "java/util/ArrayList");
+ mv.visitInsn(DUP);
+ receiver.visit(controller.getAcg());
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true);
+ controller.getOperandStack().remove(1);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "(I)V", false);
+ mv.visitVarInsn(ASTORE, var);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ receiver.visit(controller.getAcg());
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
+ controller.getOperandStack().remove(1);
+ mv.visitVarInsn(ASTORE, it);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitVarInsn(ALOAD, it);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
+ Label l3 = new Label();
+ mv.visitJumpInsn(IFEQ, l3);
+ mv.visitVarInsn(ALOAD, it);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType));
+ mv.visitVarInsn(ASTORE, next);
+ Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitVarInsn(ALOAD, var);
+ PropertyExpression pexp = propX(
+ bytecodeX(componentType, v -> v.visitVarInsn(ALOAD, next)),
+ propertyName
+ );
+ pexp.visit(controller.getAcg());
+ controller.getOperandStack().box();
+ controller.getOperandStack().remove(1);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
+ mv.visitInsn(POP);
+ Label l5 = new Label();
+ mv.visitLabel(l5);
+ mv.visitJumpInsn(GOTO, l2);
+ mv.visitLabel(l3);
+ mv.visitVarInsn(ALOAD, var);
+ if (safe) {
+ mv.visitLabel(exit);
+ }
+ controller.getOperandStack().push(ClassHelper.make(ArrayList.class));
+ controller.getCompileStack().removeVar(next);
+ controller.getCompileStack().removeVar(it);
+ controller.getCompileStack().removeVar(var);
+ }
+
+ private boolean makeGetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) {
+ FieldNode field = receiverType.getField(fieldName);
+ if (field != null) {
+ ClassNode classNode = controller.getClassNode();
+ if (field.isPrivate() && !receiverType.equals(classNode)
+ && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)
+ || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode, receiverType))) {
+ Map accessors = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS);
+ if (accessors != null) {
+ MethodNode methodNode = accessors.get(fieldName);
+ if (methodNode != null) {
+ MethodCallExpression call = callX(receiver, methodNode.getName(), args(field.isStatic() ? nullX() : receiver));
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(methodNode);
+ call.setSafe(safe);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ }
+ }
+ } else if (implicitThis) {
+ ClassNode outerClass = receiverType.getOuterClass();
+ if (outerClass != null && !receiverType.isStaticClass()) {
+ Expression expr;
+ ClassNode thisType = outerClass;
+ if (controller.isInGeneratedFunction()) {
+ while (isGeneratedFunction(thisType)) {
+ thisType = thisType.getOuterClass();
+ }
+
+ MethodCallExpression call = callThisX("getThisObject");
+ call.setImplicitThis(true);
+ call.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD);
+ call.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, thisType);
+
+ expr = castX(thisType, call);
+ } else {
+ expr = propX(classX(outerClass), "this");
+ ((PropertyExpression) expr).setImplicitThis(true);
+ }
+ expr.setSourcePosition(receiver);
+ expr.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, thisType);
+ // try again with "(Outer) getThisObject()" or "Outer.this" as receiver
+ return makeGetPrivateFieldWithBridgeMethod(expr, outerClass, fieldName, safe, true);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void makeGroovyObjectGetPropertySite(final Expression receiver, final String propertyName, final boolean safe, final boolean implicitThis) {
+ ClassNode receiverType = controller.getClassNode();
+ if (!isThisExpression(receiver) || controller.isInGeneratedFunction()) {
+ receiverType = getPropertyOwnerType(receiver); // GROOVY-9967, et al.
+ }
+
+ if (implicitThis && controller.getInvocationWriter() instanceof StaticInvocationWriter) {
+ Expression currentCall = ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall();
+ if (currentCall != null) {
+ String implicitReceiver = currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
+ if (implicitReceiver != null) {
+ String[] pathElements = implicitReceiver.split("\\.");
+ BytecodeExpression thisLoader = bytecodeX(CLOSURE_TYPE, mv -> mv.visitVarInsn(ALOAD, 0));
+ PropertyExpression pexp = propX(thisLoader, constX(pathElements[0]), safe);
+ for (int i = 1, n = pathElements.length; i < n; i += 1) {
+ pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE);
+ pexp = propX(pexp, pathElements[i]);
+ }
+ pexp.visit(controller.getAcg());
+ return;
+ }
+ }
+ }
+
+ if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, receiverType, propertyName, safe, implicitThis)) return;
+
+ MethodCallExpression call = callX(receiver, "getProperty", args(constX(propertyName)));
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD);
+ call.setSafe(safe);
+ call.visit(controller.getAcg());
+ }
+
+ @Override
+ public void makeCallSiteArrayInitializer() {
+ }
+
+ private boolean makeGetPropertyWithGetter(final Expression receiver, final ClassNode receiverType, final String propertyName, final boolean safe, final boolean implicitThis) {
+ // does a getter exist?
+ String getterName = "get" + capitalize(propertyName);
+ MethodNode getterNode = receiverType.getGetterMethod(getterName);
+ if (getterNode == null) {
+ getterName = "is" + capitalize(propertyName);
+ getterNode = receiverType.getGetterMethod(getterName);
+ }
+ if (getterNode != null && receiver instanceof ClassExpression && !CLASS_Type.equals(receiverType) && !getterNode.isStatic()) {
+ return false;
+ }
+
+ // GROOVY-5561: if two files are compiled in the same source unit
+ // and that one references the other, the getters for properties have not been
+ // generated by the compiler yet (generated by the Verifier)
+ PropertyNode propertyNode = receiverType.getProperty(propertyName);
+ if (getterNode == null && propertyNode != null) {
+ // it is possible to use a getter
+ String prefix = "get";
+ if (boolean_TYPE.equals(propertyNode.getOriginType())) {
+ prefix = "is";
+ }
+ getterName = prefix + capitalize(propertyName);
+ getterNode = new MethodNode(
+ getterName,
+ ACC_PUBLIC,
+ propertyNode.getOriginType(),
+ Parameter.EMPTY_ARRAY,
+ ClassNode.EMPTY_ARRAY,
+ EmptyStatement.INSTANCE);
+ getterNode.setDeclaringClass(receiverType);
+ if (propertyNode.isStatic()) getterNode.setModifiers(ACC_PUBLIC + ACC_STATIC);
+ }
+ if (getterNode != null) {
+ MethodCallExpression call = callX(receiver, getterName);
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(getterNode);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return true;
+ }
+
+ if (receiverType instanceof InnerClassNode && !receiverType.isStaticClass()) {
+ if (makeGetPropertyWithGetter(receiver, receiverType.getOuterClass(), propertyName, safe, implicitThis)) {
+ return true;
+ }
+ }
+
+ // check direct interfaces (GROOVY-7149)
+ for (ClassNode node : receiverType.getInterfaces()) {
+ if (makeGetPropertyWithGetter(receiver, node, propertyName, safe, implicitThis)) {
+ return true;
+ }
+ }
+ // go upper level
+ ClassNode superClass = receiverType.getSuperClass();
+ if (superClass != null) {
+ return makeGetPropertyWithGetter(receiver, superClass, propertyName, safe, implicitThis);
+ }
+
+ return false;
+ }
+
+ boolean makeGetField(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) {
+ FieldNode field = getField(receiverType, fieldName); // GROOVY-7039: include interface constants
+ if (field != null && AsmClassGenerator.isValidFieldNodeForByteCodeAccess(field, controller.getClassNode())) {
+ CompileStack compileStack = controller.getCompileStack();
+ MethodVisitor mv = controller.getMethodVisitor();
+ ClassNode replacementType = field.getOriginType();
+ OperandStack operandStack = controller.getOperandStack();
+ if (field.isStatic()) {
+ mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), fieldName, BytecodeHelper.getTypeDescription(replacementType));
+ operandStack.push(replacementType);
+ } else {
+ if (implicitThis) {
+ compileStack.pushImplicitThis(implicitThis);
+ receiver.visit(controller.getAcg());
+ compileStack.popImplicitThis();
+ } else {
+ receiver.visit(controller.getAcg());
+ }
+ Label exit = new Label();
+ if (safe) {
+ mv.visitInsn(DUP);
+ Label doGet = new Label();
+ mv.visitJumpInsn(IFNONNULL, doGet);
+ mv.visitInsn(POP);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitJumpInsn(GOTO, exit);
+ mv.visitLabel(doGet);
+ }
+ if (!operandStack.getTopOperand().isDerivedFrom(field.getOwner())) {
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner()));
+ }
+ mv.visitFieldInsn(GETFIELD, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(replacementType));
+ if (safe) {
+ if (ClassHelper.isPrimitiveType(replacementType)) {
+ operandStack.replace(replacementType);
+ operandStack.box();
+ replacementType = operandStack.getTopOperand();
+ }
+ mv.visitLabel(exit);
+ }
+ }
+ operandStack.replace(replacementType);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void makeSiteEntry() {
+ }
+
+ @Override
+ public void prepareCallSite(final String message) {
+ }
+
+ @Override
+ public void makeSingleArgumentCall(final Expression receiver, final String message, final Expression arguments, final boolean safe) {
+ TypeChooser typeChooser = controller.getTypeChooser();
+ ClassNode classNode = controller.getClassNode();
+ ClassNode rType = typeChooser.resolveType(receiver, classNode);
+ ClassNode aType = typeChooser.resolveType(arguments, classNode);
+ if (trySubscript(receiver, message, arguments, rType, aType, safe)) {
+ return;
+ }
+ // now try with flow type instead of declaration type
+ rType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ if (receiver instanceof VariableExpression && rType == null) {
+ // TODO: can STCV be made smarter to avoid this check?
+ ASTNode node = (ASTNode) ((VariableExpression) receiver).getAccessedVariable();
+ rType = node.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ }
+ if (rType!=null && trySubscript(receiver, message, arguments, rType, aType, safe)) {
+ return;
+ }
+ // todo: more cases
+ throw new GroovyBugError(
+ "At line " + receiver.getLineNumber() + " column " + receiver.getColumnNumber() + "\n" +
+ "On receiver: " + receiver.getText() + " with message: " + message + " and arguments: " + arguments.getText() + "\n" +
+ "This method should not have been called. Please try to create a simple example reproducing\n" +
+ "this error and file a bug report at https://issues.apache.org/jira/browse/GROOVY");
+ }
+
+ private boolean trySubscript(final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType, boolean safe) {
+ if (getWrapper(rType).isDerivedFrom(Number_TYPE)
+ && getWrapper(aType).isDerivedFrom(Number_TYPE)) {
+ if ("plus".equals(message) || "minus".equals(message) || "multiply".equals(message) || "div".equals(message)) {
+ writeNumberNumberCall(receiver, message, arguments);
+ return true;
+ } else if ("power".equals(message)) {
+ writePowerCall(receiver, arguments, rType, aType);
+ return true;
+ } else if ("mod".equals(message) || "leftShift".equals(message) || "rightShift".equals(message) || "rightShiftUnsigned".equals(message)
+ || "and".equals(message) || "or".equals(message) || "xor".equals(message)) {
+ writeOperatorCall(receiver, arguments, message);
+ return true;
+ }
+ } else if (STRING_TYPE.equals(rType) && "plus".equals(message)) {
+ writeStringPlusCall(receiver, message, arguments);
+ return true;
+ } else if ("getAt".equals(message)) {
+ if (rType.isArray() && getWrapper(aType).isDerivedFrom(Number_TYPE) && !safe) {
+ writeArrayGet(receiver, arguments, rType, aType);
+ return true;
+ } else {
+ // check if a getAt method can be found on the receiver
+ ClassNode current = rType;
+ MethodNode getAtNode = null;
+ while (current != null && getAtNode == null) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(aType, "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", aType);
+ }
+ if (getAtNode == null && isPrimitiveType(aType)) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getWrapper(aType), "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", getWrapper(aType));
+ }
+ } else if (getAtNode == null && aType.isDerivedFrom(Number_TYPE)) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getUnwrapper(aType), "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", getUnwrapper(aType));
+ }
+ }
+ current = current.getSuperClass();
+ }
+ if (getAtNode != null) {
+ MethodCallExpression call = callX(receiver, "getAt", arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(getAtNode);
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+
+ // make sure Map#getAt() and List#getAt handled with the bracket syntax are properly compiled
+ ClassNode[] args = {aType};
+ boolean acceptAnyMethod =
+ MAP_TYPE.equals(rType) || rType.implementsInterface(MAP_TYPE)
+ || LIST_TYPE.equals(rType) || rType.implementsInterface(LIST_TYPE);
+ List nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args);
+ if (nodes.isEmpty()) {
+ // retry with raw types
+ rType = rType.getPlainNodeReference();
+ nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args);
+ }
+ if (nodes.size() == 1 || (nodes.size() > 1 && acceptAnyMethod)) {
+ MethodCallExpression call = callX(receiver, message, arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(nodes.get(0));
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) {
+ // fallback to Map#get
+ MethodCallExpression call = callX(receiver, "get", arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(MAP_GET_METHOD);
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private MethodNode getCompatibleMethod(final ClassNode current, final String getAt, final ClassNode aType) {
+ // TODO this really should find "best" match or find all matches and complain about ambiguity if more than one
+ // TODO handle getAt with more than one parameter
+ // TODO handle default getAt methods on Java 8 interfaces
+ for (MethodNode methodNode : current.getDeclaredMethods("getAt")) {
+ if (methodNode.getParameters().length == 1) {
+ ClassNode paramType = methodNode.getParameters()[0].getType();
+ if (aType.isDerivedFrom(paramType) || aType.declaresInterface(paramType)) {
+ return methodNode;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void writeArrayGet(final Expression receiver, final Expression arguments, final ClassNode rType, final ClassNode aType) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // visit receiver
+ receiver.visit(controller.getAcg());
+ // visit arguments as array index
+ arguments.visit(controller.getAcg());
+ operandStack.doGroovyCast(int_TYPE);
+ int m2 = operandStack.getStackLength();
+ // array access
+ controller.getMethodVisitor().visitInsn(AALOAD);
+ operandStack.replace(rType.getComponentType(), m2 - m1);
+ }
+
+ private void writeOperatorCall(final Expression receiver, final Expression arguments, final String operator) {
+ prepareSiteAndReceiver(receiver, operator, false, controller.getCompileStack().isLHS());
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ visitBoxedArgument(arguments);
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/typehandling/NumberMath", operator, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ controller.getOperandStack().replace(Number_TYPE, 2);
+ }
+
+ private void writePowerCall(final Expression receiver, final Expression arguments, final ClassNode rType, final ClassNode aType) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, "power", false, controller.getCompileStack().isLHS());
+ operandStack.doGroovyCast(getWrapper(rType));
+ visitBoxedArgument(arguments);
+ operandStack.doGroovyCast(getWrapper(aType));
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ if (BigDecimal_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigDecimal;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (BigInteger_TYPE.equals(rType) && Integer_TYPE.equals(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigInteger;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (Long_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Long;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (Integer_TYPE.equals(getWrapper(rType)) && Integer_TYPE.equals(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ }
+ controller.getOperandStack().replace(Number_TYPE, m2 - m1);
+ }
+
+ private void writeStringPlusCall(final Expression receiver, final String message, final Expression arguments) {
+ // TODO: performance would be better if we created a StringBuilder
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
+ visitBoxedArgument(arguments);
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "plus", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false);
+ controller.getOperandStack().replace(STRING_TYPE, m2 - m1);
+ }
+
+ private void writeNumberNumberCall(final Expression receiver, final String message, final Expression arguments) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ visitBoxedArgument(arguments);
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/dgmimpl/NumberNumber" + capitalize(message), message, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ controller.getOperandStack().replace(Number_TYPE, m2 - m1);
+ }
+
+ @Override
+ public void fallbackAttributeOrPropertySite(final PropertyExpression expression, final Expression objectExpression, final String name, final MethodCallerMultiAdapter adapter) {
+ CompileStack compileStack = controller.getCompileStack();
+ OperandStack operandStack = controller.getOperandStack();
+
+ if (name != null && compileStack.isLHS()) {
+ boolean[] isClassReceiver = new boolean[1];
+ ClassNode receiverType = getPropertyOwnerType(objectExpression, isClassReceiver);
+ if (adapter == AsmClassGenerator.setField || adapter == AsmClassGenerator.setGroovyObjectField) {
+ if (setField(expression, objectExpression, receiverType, name)) return;
+ }
+ if (isThisExpression(objectExpression)) {
+ ClassNode classNode = controller.getClassNode();
+ FieldNode fieldNode = receiverType.getField(name);
+ if (fieldNode != null && fieldNode.isPrivate() && !receiverType.equals(classNode)
+ && StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)) {
+ Map mutators = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS);
+ if (mutators != null) {
+ MethodNode methodNode = mutators.get(name);
+ if (methodNode != null) {
+ ClassNode rhsType = operandStack.getTopOperand();
+ int i = compileStack.defineTemporaryVariable("$rhsValue", rhsType, true);
+ VariableSlotLoader rhsValue = new VariableSlotLoader(rhsType, i, operandStack);
+
+ MethodCallExpression call = callX(objectExpression, methodNode.getName(), args(fieldNode.isStatic() ? nullX() : objectExpression, rhsValue));
+ call.setImplicitThis(expression.isImplicitThis());
+ call.setSpreadSafe(expression.isSpreadSafe());
+ call.setSafe(expression.isSafe());
+ call.setMethodTarget(methodNode);
+ call.visit(controller.getAcg());
+
+ // GROOVY-9892: assuming that the mutator method has a return value, make sure the operand
+ // stack is not polluted with the result of the method call
+ operandStack.pop();
+
+ compileStack.removeVar(i);
+ return;
+ }
+ }
+ }
+ }
+ // GRECLIPSE add -- GROOVY-6954
+ if (isOrImplements(receiverType, MAP_TYPE) && !isClassReceiver[0]) {
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ // store value in temporary variable
+ ClassNode rhsType = operandStack.getTopOperand();
+ int rhs = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
+
+ // push receiver on stack
+ compileStack.pushLHS(false);
+ objectExpression.visit(controller.getAcg());
+ compileStack.popLHS();
+
+ // check if receiver null
+ Label skip = new Label();
+ if (expression.isSafe()) {
+ mv.visitInsn(DUP);
+ mv.visitJumpInsn(IFNULL, skip);
+ }
+
+ mv.visitLdcInsn(name);
+ BytecodeHelper.load(mv, rhsType, rhs);
+ if (isPrimitiveType(rhsType)) BytecodeHelper.doCastToWrappedType(mv, rhsType, getWrapper(rhsType));
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
+
+ if (expression.isSafe()) {
+ mv.visitLabel(skip);
+ }
+ // no return value
+ operandStack.pop();
+ compileStack.removeVar(rhs);
+ return;
+ }
+
+ // GRECLIPSE end
+ }
+ super.fallbackAttributeOrPropertySite(expression, objectExpression, name, adapter);
+ }
+
+ private ClassNode getPropertyOwnerType(final Expression receiver, final boolean... isClassReceiver) {
+ Object inferredType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ if (inferredType == null && receiver instanceof VariableExpression) {
+ Variable variable = ((VariableExpression) receiver).getAccessedVariable();
+ if (variable instanceof Expression) {
+ inferredType = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ }
+ }
+ ClassNode receiverType;
+ if (inferredType instanceof ClassNode) {
+ // in case a "flow type" is found, it is preferred to use it instead of the declaration type
+ receiverType = (ClassNode) inferredType;
+ } else {
+ receiverType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
+ if (receiverType == null) {
+ receiverType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
+ }
+ }
+ if (isClassClassNodeWrappingConcreteType(receiverType)) {
+ if (isClassReceiver.length > 0) isClassReceiver[0] = true;
+ receiverType = receiverType.getGenericsTypes()[0].getType();
+ }
+ if (isPrimitiveType(receiverType)) {
+ // GROOVY-6590: wrap primitive types
+ receiverType = getWrapper(receiverType);
+ }
+ return receiverType;
+ }
+
+ // this is just a simple set field handling static and non-static, but not Closure and inner classes
+ private boolean setField(final PropertyExpression expression, final Expression objectExpression, final ClassNode receiverType, final String name) {
+ if (expression.isSafe()) return false;
+ FieldNode fn = AsmClassGenerator.getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(controller.getClassNode(), receiverType, name, false);
+ if (fn == null) return false;
+ OperandStack stack = controller.getOperandStack();
+ stack.doGroovyCast(fn.getType());
+
+ MethodVisitor mv = controller.getMethodVisitor();
+ if (!fn.isStatic()) {
+ controller.getCompileStack().pushLHS(false);
+ objectExpression.visit(controller.getAcg());
+ controller.getCompileStack().popLHS();
+ if (!receiverType.equals(stack.getTopOperand())) {
+ BytecodeHelper.doCast(mv, receiverType);
+ stack.replace(receiverType);
+ }
+ stack.swap();
+ mv.visitFieldInsn(PUTFIELD, BytecodeHelper.getClassInternalName(fn.getOwner()), name, BytecodeHelper.getTypeDescription(fn.getType()));
+ stack.remove(1);
+ } else {
+ mv.visitFieldInsn(PUTSTATIC, BytecodeHelper.getClassInternalName(receiverType), name, BytecodeHelper.getTypeDescription(fn.getType()));
+ }
+
+ return true;
+ }
+
+ /*private boolean getField(final PropertyExpression expression, final Expression receiver, ClassNode receiverType, final String name) {
+ boolean safe = expression.isSafe();
+ boolean implicitThis = expression.isImplicitThis();
+
+ if (makeGetField(receiver, receiverType, name, safe, implicitThis)) return true;
+ if (receiver instanceof ClassExpression) {
+ if (makeGetField(receiver, receiver.getType(), name, safe, implicitThis)) return true;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), name, safe, implicitThis)) return true;
+ }
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, name, safe, implicitThis)) return true;
+
+ boolean isClassReceiver = false;
+ if (isClassClassNodeWrappingConcreteType(receiverType)) {
+ isClassReceiver = true;
+ receiverType = receiverType.getGenericsTypes()[0].getType();
+ }
+ if (isClassReceiver && makeGetField(receiver, CLASS_Type, name, safe, false)) return true;
+ if (receiverType.isEnum()) {
+ controller.getMethodVisitor().visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), name, BytecodeHelper.getTypeDescription(receiverType));
+ controller.getOperandStack().push(receiverType);
+ return true;
+ }
+ return false;
+ }*/
+
+ private void addPropertyAccessError(final Expression receiver, final String propertyName, final ClassNode receiverType) {
+ String receiverName = (receiver instanceof ClassExpression ? receiver.getType() : receiverType).toString(false);
+ String message = "Access to " + receiverName + "#" + propertyName + " is forbidden";
+ controller.getSourceUnit().addError(new SyntaxException(message, receiver));
+ }
+}
diff --git a/base/org.codehaus.groovy40/.checkstyle b/base/org.codehaus.groovy40/.checkstyle
index c71f98e84f..999b33020d 100644
--- a/base/org.codehaus.groovy40/.checkstyle
+++ b/base/org.codehaus.groovy40/.checkstyle
@@ -40,7 +40,7 @@
-
+
diff --git a/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
new file mode 100644
index 0000000000..93a4a889f7
--- /dev/null
+++ b/base/org.codehaus.groovy40/src/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
@@ -0,0 +1,948 @@
+/*
+ * 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 org.codehaus.groovy.classgen.asm.sc;
+
+import org.codehaus.groovy.GroovyBugError;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.classgen.AsmClassGenerator;
+import org.codehaus.groovy.classgen.BytecodeExpression;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.codehaus.groovy.classgen.asm.CallSiteWriter;
+import org.codehaus.groovy.classgen.asm.CompileStack;
+import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
+import org.codehaus.groovy.classgen.asm.OperandStack;
+import org.codehaus.groovy.classgen.asm.TypeChooser;
+import org.codehaus.groovy.classgen.asm.VariableSlotLoader;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
+import org.codehaus.groovy.transform.stc.StaticTypesMarker;
+import groovyjarjarasm.asm.Label;
+import groovyjarjarasm.asm.MethodVisitor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.groovy.ast.tools.ClassNodeUtils.getField;
+import static org.apache.groovy.ast.tools.ExpressionUtils.isThisExpression;
+import static org.apache.groovy.util.BeanUtils.capitalize;
+import static org.codehaus.groovy.ast.ClassHelper.CLASS_Type;
+import static org.codehaus.groovy.ast.ClassHelper.CLOSURE_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.GROOVY_OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Iterator_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.LIST_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.MAP_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.Number_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.OBJECT_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.STRING_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.getUnwrapper;
+import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
+import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
+import static org.codehaus.groovy.ast.ClassHelper.isBigDecimalType;
+import static org.codehaus.groovy.ast.ClassHelper.isBigIntegerType;
+import static org.codehaus.groovy.ast.ClassHelper.isClassType;
+import static org.codehaus.groovy.ast.ClassHelper.isGeneratedFunction;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveBoolean;
+import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
+import static org.codehaus.groovy.ast.ClassHelper.isStringType;
+import static org.codehaus.groovy.ast.ClassHelper.isWrapperInteger;
+import static org.codehaus.groovy.ast.ClassHelper.isWrapperLong;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.args;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.bytecodeX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callThisX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.callX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.castX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.classX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.isOrImplements;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.nullX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.propX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.varX;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
+import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.isClassClassNodeWrappingConcreteType;
+import static groovyjarjarasm.asm.Opcodes.AALOAD;
+import static groovyjarjarasm.asm.Opcodes.ACC_PUBLIC;
+import static groovyjarjarasm.asm.Opcodes.ACC_STATIC;
+import static groovyjarjarasm.asm.Opcodes.ACONST_NULL;
+import static groovyjarjarasm.asm.Opcodes.ALOAD;
+import static groovyjarjarasm.asm.Opcodes.ARRAYLENGTH;
+import static groovyjarjarasm.asm.Opcodes.ASTORE;
+import static groovyjarjarasm.asm.Opcodes.CHECKCAST;
+import static groovyjarjarasm.asm.Opcodes.DUP;
+import static groovyjarjarasm.asm.Opcodes.GETFIELD;
+import static groovyjarjarasm.asm.Opcodes.GETSTATIC;
+import static groovyjarjarasm.asm.Opcodes.GOTO;
+import static groovyjarjarasm.asm.Opcodes.IFEQ;
+import static groovyjarjarasm.asm.Opcodes.IFNONNULL;
+import static groovyjarjarasm.asm.Opcodes.IFNULL;
+import static groovyjarjarasm.asm.Opcodes.INVOKEINTERFACE;
+import static groovyjarjarasm.asm.Opcodes.INVOKESPECIAL;
+import static groovyjarjarasm.asm.Opcodes.INVOKESTATIC;
+import static groovyjarjarasm.asm.Opcodes.NEW;
+import static groovyjarjarasm.asm.Opcodes.POP;
+import static groovyjarjarasm.asm.Opcodes.PUTFIELD;
+import static groovyjarjarasm.asm.Opcodes.PUTSTATIC;
+
+/**
+ * A call site writer which replaces call site caching with static calls. This means that the generated code
+ * looks more like Java code than dynamic Groovy code. Best effort is made to use JVM instructions instead of
+ * calls to helper methods.
+ */
+public class StaticTypesCallSiteWriter extends CallSiteWriter {
+
+ private static final ClassNode COLLECTION_TYPE = ClassHelper.make(Collection.class);
+ private static final ClassNode INVOKERHELPER_TYPE = ClassHelper.make(InvokerHelper.class);
+ private static final MethodNode COLLECTION_SIZE_METHOD = COLLECTION_TYPE.getMethod("size", Parameter.EMPTY_ARRAY);
+ private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", Parameter.EMPTY_ARRAY);
+ private static final MethodNode MAP_GET_METHOD = MAP_TYPE.getMethod("get", new Parameter[]{new Parameter(OBJECT_TYPE, "key")});
+ private static final MethodNode GROOVYOBJECT_GETPROPERTY_METHOD = GROOVY_OBJECT_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(STRING_TYPE, "propertyName")});
+ private static final MethodNode INVOKERHELPER_GETPROPERTY_METHOD = INVOKERHELPER_TYPE.getMethod("getProperty", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")});
+ private static final MethodNode INVOKERHELPER_GETPROPERTYSAFE_METHOD = INVOKERHELPER_TYPE.getMethod("getPropertySafe", new Parameter[]{new Parameter(OBJECT_TYPE, "object"), new Parameter(STRING_TYPE, "propertyName")});
+
+ private final StaticTypesWriterController controller;
+
+ public StaticTypesCallSiteWriter(final StaticTypesWriterController controller) {
+ super(controller);
+ this.controller = controller;
+ }
+
+ @Override
+ public void generateCallSiteArray() {
+ CallSiteWriter regularCallSiteWriter = controller.getRegularCallSiteWriter();
+ if (regularCallSiteWriter.hasCallSiteUse()) {
+ regularCallSiteWriter.generateCallSiteArray();
+ }
+ }
+
+ @Override
+ public void makeCallSite(final Expression receiver, final String message, final Expression arguments, final boolean safe, final boolean implicitThis, final boolean callCurrent, final boolean callStatic) {
+ }
+
+ @Override
+ public void makeGetPropertySite(final Expression receiver, final String propertyName, final boolean safe, final boolean implicitThis) {
+ Object dynamic = receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY);
+ if (dynamic != null) {
+ makeDynamicGetProperty(receiver, propertyName, safe);
+ return;
+ }
+ boolean[] isClassReceiver = new boolean[1];
+ ClassNode receiverType = getPropertyOwnerType(receiver, isClassReceiver);
+ if (receiverType.isArray() && "length".equals(propertyName)) {
+ receiver.visit(controller.getAcg());
+ ClassNode arrayGetReturnType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
+ controller.getOperandStack().doGroovyCast(arrayGetReturnType);
+ controller.getMethodVisitor().visitInsn(ARRAYLENGTH);
+ controller.getOperandStack().replace(int_TYPE);
+ return;
+ } else if (isOrImplements(receiverType, COLLECTION_TYPE) && ("size".equals(propertyName) || "length".equals(propertyName))) {
+ MethodCallExpression expr = callX(receiver, "size");
+ expr.setMethodTarget(COLLECTION_SIZE_METHOD);
+ expr.setImplicitThis(implicitThis);
+ expr.setSafe(safe);
+ expr.visit(controller.getAcg());
+ return;
+ }
+
+ boolean isStaticProperty = receiver instanceof ClassExpression
+ && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType()));
+
+ if (!isStaticProperty && isOrImplements(receiverType, MAP_TYPE)) {
+ // for maps, replace map.foo with map.get('foo')
+ writeMapDotProperty(receiver, propertyName, safe);
+ return;
+ }
+ if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (receiver instanceof ClassExpression) {
+ if (makeGetField(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ if (makeGetPropertyWithGetter(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), propertyName, safe, implicitThis)) return;
+ }
+ if (isClassReceiver[0]) {
+ // we are probably looking for a property of the class
+ if (makeGetPropertyWithGetter(receiver, CLASS_Type, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, CLASS_Type, propertyName, safe, false)) return;
+ }
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, propertyName, safe, implicitThis)) return;
+
+ // GROOVY-5580: it is still possible that we're calling a superinterface property
+ String isserName = "is" + capitalize(propertyName);
+ String getterName = "get" + capitalize(propertyName);
+ if (receiverType.isInterface()) {
+ MethodNode getterMethod = null;
+ for (ClassNode anInterface : receiverType.getAllInterfaces()) {
+ getterMethod = anInterface.getGetterMethod(isserName);
+ if (getterMethod == null)
+ getterMethod = anInterface.getGetterMethod(getterName);
+ if (getterMethod != null) break;
+ }
+ // GROOVY-5585
+ if (getterMethod == null) {
+ getterMethod = OBJECT_TYPE.getGetterMethod(getterName);
+ }
+ if (getterMethod != null) {
+ MethodCallExpression call = callX(receiver, getterName);
+ call.setImplicitThis(false);
+ call.setMethodTarget(getterMethod);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return;
+ }
+ }
+
+ // GROOVY-5568: we would be facing a DGM call, but instead of foo.getText(), have foo.text
+ List methods = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, isserName, ClassNode.EMPTY_ARRAY);
+ methods.removeIf(dgm -> !isPrimitiveBoolean(dgm.getReturnType()));
+ findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), receiverType, getterName, ClassNode.EMPTY_ARRAY, methods);
+ if (!methods.isEmpty()) {
+ List methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY);
+ if (methodNodes.size() == 1) {
+ MethodNode getter = methodNodes.get(0);
+ MethodCallExpression call = callX(receiver, getter.getName());
+ call.setImplicitThis(false);
+ call.setMethodTarget(getter);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return;
+ }
+ }
+
+ if (!isStaticProperty && isOrImplements(receiverType, LIST_TYPE)) {
+ writeListDotProperty(receiver, propertyName, safe);
+ return;
+ }
+
+ addPropertyAccessError(receiver, propertyName, receiverType);
+ controller.getMethodVisitor().visitInsn(ACONST_NULL);
+ controller.getOperandStack().push(OBJECT_TYPE);
+ }
+
+ private void makeDynamicGetProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ MethodNode target = safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD;
+ MethodCallExpression call = callX(
+ classX(INVOKERHELPER_TYPE),
+ target.getName(),
+ args(receiver, constX(propertyName))
+ );
+ call.setImplicitThis(false);
+ call.setMethodTarget(target);
+ call.visit(controller.getAcg());
+ }
+
+ private void writeMapDotProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ // push receiver on stack
+ receiver.visit(controller.getAcg());
+
+ // check if receiver null
+ Label skip = new Label();
+ if (safe) {
+ mv.visitInsn(DUP);
+ mv.visitJumpInsn(IFNULL, skip);
+ }
+
+ mv.visitLdcInsn(propertyName);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;", true);
+
+ if (safe) {
+ mv.visitLabel(skip);
+ }
+ controller.getOperandStack().replace(OBJECT_TYPE);
+ }
+
+ private void writeListDotProperty(final Expression receiver, final String propertyName, final boolean safe) {
+ // for lists, replace list.foo with:
+ // def result = new ArrayList(list.size())
+ // for (item in list) result.add(item.foo)
+ // result
+ ClassNode componentType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE);
+ if (componentType == null) {
+ componentType = OBJECT_TYPE;
+ }
+ CompileStack compileStack = controller.getCompileStack();
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ Label exit = new Label();
+ if (safe) {
+ receiver.visit(controller.getAcg());
+ Label doGet = new Label();
+ mv.visitJumpInsn(IFNONNULL, doGet);
+ controller.getOperandStack().remove(1);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitJumpInsn(GOTO, exit);
+ mv.visitLabel(doGet);
+ }
+
+ Variable tmpList = varX("tmpList", ClassHelper.make(ArrayList.class));
+ int var = compileStack.defineTemporaryVariable(tmpList, false);
+ Variable iterator = varX("iterator", Iterator_TYPE);
+ int it = compileStack.defineTemporaryVariable(iterator, false);
+ Variable nextVar = varX("next", componentType);
+ final int next = compileStack.defineTemporaryVariable(nextVar, false);
+
+ mv.visitTypeInsn(NEW, "java/util/ArrayList");
+ mv.visitInsn(DUP);
+ receiver.visit(controller.getAcg());
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true);
+ controller.getOperandStack().remove(1);
+ mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "", "(I)V", false);
+ mv.visitVarInsn(ASTORE, var);
+ Label l1 = new Label();
+ mv.visitLabel(l1);
+ receiver.visit(controller.getAcg());
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
+ controller.getOperandStack().remove(1);
+ mv.visitVarInsn(ASTORE, it);
+ Label l2 = new Label();
+ mv.visitLabel(l2);
+ mv.visitVarInsn(ALOAD, it);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
+ Label l3 = new Label();
+ mv.visitJumpInsn(IFEQ, l3);
+ mv.visitVarInsn(ALOAD, it);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType));
+ mv.visitVarInsn(ASTORE, next);
+ Label l4 = new Label();
+ mv.visitLabel(l4);
+ mv.visitVarInsn(ALOAD, var);
+ PropertyExpression pexp = propX(
+ bytecodeX(componentType, v -> v.visitVarInsn(ALOAD, next)),
+ propertyName
+ );
+ pexp.visit(controller.getAcg());
+ controller.getOperandStack().box();
+ controller.getOperandStack().remove(1);
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
+ mv.visitInsn(POP);
+ Label l5 = new Label();
+ mv.visitLabel(l5);
+ mv.visitJumpInsn(GOTO, l2);
+ mv.visitLabel(l3);
+ mv.visitVarInsn(ALOAD, var);
+ if (safe) {
+ mv.visitLabel(exit);
+ }
+ controller.getOperandStack().push(ClassHelper.make(ArrayList.class));
+ controller.getCompileStack().removeVar(next);
+ controller.getCompileStack().removeVar(it);
+ controller.getCompileStack().removeVar(var);
+ }
+
+ private boolean makeGetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) {
+ FieldNode field = receiverType.getField(fieldName);
+ if (field != null) {
+ ClassNode classNode = controller.getClassNode();
+ if (field.isPrivate() && !receiverType.equals(classNode)
+ && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)
+ || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode, receiverType))) {
+ Map accessors = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS);
+ if (accessors != null) {
+ MethodNode methodNode = accessors.get(fieldName);
+ if (methodNode != null) {
+ MethodCallExpression call = callX(receiver, methodNode.getName(), args(field.isStatic() ? nullX() : receiver));
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(methodNode);
+ call.setSafe(safe);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ }
+ }
+ } else if (implicitThis) {
+ ClassNode outerClass = receiverType.getOuterClass();
+ if (outerClass != null && !receiverType.isStaticClass()) {
+ Expression expr;
+ ClassNode thisType = outerClass;
+ if (controller.isInGeneratedFunction()) {
+ while (isGeneratedFunction(thisType)) {
+ thisType = thisType.getOuterClass();
+ }
+
+ MethodCallExpression call = callThisX("getThisObject");
+ call.setImplicitThis(true);
+ call.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD);
+ call.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, thisType);
+
+ expr = castX(thisType, call);
+ } else {
+ expr = propX(classX(outerClass), "this");
+ ((PropertyExpression) expr).setImplicitThis(true);
+ }
+ expr.setSourcePosition(receiver);
+ expr.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, thisType);
+ // try again with "(Outer) getThisObject()" or "Outer.this" as receiver
+ return makeGetPrivateFieldWithBridgeMethod(expr, outerClass, fieldName, safe, true);
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void makeGroovyObjectGetPropertySite(final Expression receiver, final String propertyName, final boolean safe, final boolean implicitThis) {
+ ClassNode receiverType = controller.getClassNode();
+ if (!isThisExpression(receiver) || controller.isInGeneratedFunction()) {
+ receiverType = getPropertyOwnerType(receiver); // GROOVY-9967, et al.
+ }
+
+ if (implicitThis && controller.getInvocationWriter() instanceof StaticInvocationWriter) {
+ Expression currentCall = ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall();
+ if (currentCall != null) {
+ String implicitReceiver = currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER);
+ if (implicitReceiver != null) {
+ String[] pathElements = implicitReceiver.split("\\.");
+ BytecodeExpression thisLoader = bytecodeX(CLOSURE_TYPE, mv -> mv.visitVarInsn(ALOAD, 0));
+ PropertyExpression pexp = propX(thisLoader, constX(pathElements[0]), safe);
+ for (int i = 1, n = pathElements.length; i < n; i += 1) {
+ pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE);
+ pexp = propX(pexp, pathElements[i]);
+ }
+ pexp.visit(controller.getAcg());
+ return;
+ }
+ }
+ }
+
+ if (makeGetPropertyWithGetter(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, propertyName, safe, implicitThis)) return;
+ if (makeGetField(receiver, receiverType, propertyName, safe, implicitThis)) return;
+
+ boolean isScriptVariable = (receiverType.isScript() && receiver instanceof VariableExpression && ((VariableExpression) receiver).getAccessedVariable() == null);
+ if (!isScriptVariable && controller.getClassNode().getOuterClass() == null) { // inner class still needs dynamic property sequence
+ addPropertyAccessError(receiver, propertyName, receiverType);
+ }
+
+ MethodCallExpression call = callX(receiver, "getProperty", args(constX(propertyName)));
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD);
+ call.setSafe(safe);
+ call.visit(controller.getAcg());
+ }
+
+ @Override
+ public void makeCallSiteArrayInitializer() {
+ }
+
+ private boolean makeGetPropertyWithGetter(final Expression receiver, final ClassNode receiverType, final String propertyName, final boolean safe, final boolean implicitThis) {
+ // check for an accessor method
+ String getterName = "is" + capitalize(propertyName);
+ MethodNode getterNode = receiverType.getGetterMethod(getterName);
+ if (getterNode == null) {
+ getterName = "get" + capitalize(propertyName);
+ getterNode = receiverType.getGetterMethod(getterName);
+ }
+ if (getterNode != null && receiver instanceof ClassExpression && !isClassType(receiverType) && !getterNode.isStatic()) {
+ return false;
+ }
+
+ // GROOVY-5561: if two files are compiled in the same source unit
+ // and that one references the other, the getters for properties have not been
+ // generated by the compiler yet (generated by the Verifier)
+ PropertyNode propertyNode = receiverType.getProperty(propertyName);
+ if (getterNode == null && propertyNode != null) {
+ // it is possible to use an accessor method
+ String prefix = isPrimitiveBoolean(propertyNode.getOriginType()) ? "is" : "get";
+ getterName = prefix + capitalize(propertyName);
+ getterNode = new MethodNode(
+ getterName,
+ ACC_PUBLIC,
+ propertyNode.getOriginType(),
+ Parameter.EMPTY_ARRAY,
+ ClassNode.EMPTY_ARRAY,
+ EmptyStatement.INSTANCE);
+ getterNode.setDeclaringClass(receiverType);
+ if (propertyNode.isStatic()) getterNode.setModifiers(ACC_PUBLIC + ACC_STATIC);
+ }
+ if (getterNode != null) {
+ MethodCallExpression call = callX(receiver, getterName);
+ call.setImplicitThis(implicitThis);
+ call.setMethodTarget(getterNode);
+ call.setSafe(safe);
+ call.setSourcePosition(receiver);
+ call.visit(controller.getAcg());
+ return true;
+ }
+
+ if (receiverType instanceof InnerClassNode && !receiverType.isStaticClass()) {
+ if (makeGetPropertyWithGetter(receiver, receiverType.getOuterClass(), propertyName, safe, implicitThis)) {
+ return true;
+ }
+ }
+
+ // check direct interfaces (GROOVY-7149)
+ for (ClassNode node : receiverType.getInterfaces()) {
+ if (makeGetPropertyWithGetter(receiver, node, propertyName, safe, implicitThis)) {
+ return true;
+ }
+ }
+ // go upper level
+ ClassNode superClass = receiverType.getSuperClass();
+ if (superClass != null) {
+ return makeGetPropertyWithGetter(receiver, superClass, propertyName, safe, implicitThis);
+ }
+
+ return false;
+ }
+
+ boolean makeGetField(final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) {
+ FieldNode field = getField(receiverType, fieldName); // GROOVY-7039: include interface constants
+ if (field != null && AsmClassGenerator.isFieldDirectlyAccessible(field, controller.getClassNode())) {
+ CompileStack compileStack = controller.getCompileStack();
+ OperandStack operandStack = controller.getOperandStack();
+ MethodVisitor mv = controller.getMethodVisitor();
+ ClassNode resultType = field.getOriginType();
+ if (field.isStatic()) {
+ mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), fieldName, BytecodeHelper.getTypeDescription(resultType));
+ operandStack.push(resultType);
+ } else {
+ if (implicitThis) {
+ compileStack.pushImplicitThis(true);
+ receiver.visit(controller.getAcg());
+ compileStack.popImplicitThis();
+ } else {
+ receiver.visit(controller.getAcg());
+ }
+ Label skip = new Label();
+ if (safe) {
+ mv.visitInsn(DUP);
+ Label doGet = new Label();
+ mv.visitJumpInsn(IFNONNULL, doGet);
+ mv.visitInsn(POP);
+ mv.visitInsn(ACONST_NULL);
+ mv.visitJumpInsn(GOTO, skip);
+ mv.visitLabel(doGet);
+ }
+ if (!operandStack.getTopOperand().isDerivedFrom(field.getOwner())) {
+ mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner()));
+ }
+ mv.visitFieldInsn(GETFIELD, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(resultType));
+ if (safe) {
+ if (isPrimitiveType(resultType)) {
+ operandStack.replace(resultType);
+ operandStack.box();
+ resultType = operandStack.getTopOperand();
+ }
+ mv.visitLabel(skip);
+ }
+ }
+ operandStack.replace(resultType);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void makeSiteEntry() {
+ }
+
+ @Override
+ public void prepareCallSite(final String message) {
+ }
+
+ @Override
+ public void makeSingleArgumentCall(final Expression receiver, final String message, final Expression arguments, final boolean safe) {
+ TypeChooser typeChooser = controller.getTypeChooser();
+ ClassNode classNode = controller.getClassNode();
+ ClassNode rType = typeChooser.resolveType(receiver, classNode);
+ ClassNode aType = typeChooser.resolveType(arguments, classNode);
+ if (trySubscript(receiver, message, arguments, rType, aType, safe)) {
+ return;
+ }
+ // now try with flow type instead of declaration type
+ rType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ if (receiver instanceof VariableExpression && rType == null) {
+ // TODO: can STCV be made smarter to avoid this check?
+ ASTNode node = (ASTNode) ((VariableExpression) receiver).getAccessedVariable();
+ rType = node.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ }
+ if (rType!=null && trySubscript(receiver, message, arguments, rType, aType, safe)) {
+ return;
+ }
+ // todo: more cases
+ throw new GroovyBugError(
+ "At line " + receiver.getLineNumber() + " column " + receiver.getColumnNumber() + "\n" +
+ "On receiver: " + receiver.getText() + " with message: " + message + " and arguments: " + arguments.getText() + "\n" +
+ "This method should not have been called. Please try to create a simple example reproducing\n" +
+ "this error and file a bug report at https://issues.apache.org/jira/browse/GROOVY");
+ }
+
+ private boolean trySubscript(final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType, boolean safe) {
+ if (getWrapper(rType).isDerivedFrom(Number_TYPE)
+ && getWrapper(aType).isDerivedFrom(Number_TYPE)) {
+ if ("plus".equals(message) || "minus".equals(message) || "multiply".equals(message) || "div".equals(message)) {
+ writeNumberNumberCall(receiver, message, arguments);
+ return true;
+ } else if ("power".equals(message)) {
+ writePowerCall(receiver, arguments, rType, aType);
+ return true;
+ } else if ("mod".equals(message) || "leftShift".equals(message) || "rightShift".equals(message) || "rightShiftUnsigned".equals(message)
+ || "and".equals(message) || "or".equals(message) || "xor".equals(message)) {
+ writeOperatorCall(receiver, arguments, message);
+ return true;
+ }
+ } else if (isStringType(rType) && "plus".equals(message)) {
+ writeStringPlusCall(receiver, message, arguments);
+ return true;
+ } else if ("getAt".equals(message)) {
+ if (rType.isArray() && getWrapper(aType).isDerivedFrom(Number_TYPE) && !safe) {
+ writeArrayGet(receiver, arguments, rType, aType);
+ return true;
+ } else {
+ // check if a getAt method can be found on the receiver
+ ClassNode current = rType;
+ MethodNode getAtNode = null;
+ while (current != null && getAtNode == null) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(aType, "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", aType);
+ }
+ if (getAtNode == null && isPrimitiveType(aType)) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getWrapper(aType), "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", getWrapper(aType));
+ }
+ } else if (getAtNode == null && aType.isDerivedFrom(Number_TYPE)) {
+ getAtNode = current.getDeclaredMethod("getAt", new Parameter[]{new Parameter(getUnwrapper(aType), "index")});
+ if (getAtNode == null) {
+ getAtNode = getCompatibleMethod(current, "getAt", getUnwrapper(aType));
+ }
+ }
+ current = current.getSuperClass();
+ }
+ if (getAtNode != null) {
+ MethodCallExpression call = callX(receiver, "getAt", arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(getAtNode);
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+
+ // make sure Map#getAt() and List#getAt handled with the bracket syntax are properly compiled
+ ClassNode[] args = {aType};
+ boolean acceptAnyMethod =
+ MAP_TYPE.equals(rType) || rType.implementsInterface(MAP_TYPE)
+ || LIST_TYPE.equals(rType) || rType.implementsInterface(LIST_TYPE);
+ List nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args);
+ if (nodes.isEmpty()) {
+ // retry with raw types
+ rType = rType.getPlainNodeReference();
+ nodes = findDGMMethodsByNameAndArguments(controller.getSourceUnit().getClassLoader(), rType, message, args);
+ }
+ if (nodes.size() == 1 || (nodes.size() > 1 && acceptAnyMethod)) {
+ MethodCallExpression call = callX(receiver, message, arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(nodes.get(0));
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) {
+ // fallback to Map#get
+ MethodCallExpression call = callX(receiver, "get", arguments);
+ call.setImplicitThis(false);
+ call.setMethodTarget(MAP_GET_METHOD);
+ call.setSafe(safe);
+ call.setSourcePosition(arguments);
+ call.visit(controller.getAcg());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private MethodNode getCompatibleMethod(final ClassNode current, final String getAt, final ClassNode aType) {
+ // TODO this really should find "best" match or find all matches and complain about ambiguity if more than one
+ // TODO handle getAt with more than one parameter
+ // TODO handle default getAt methods on Java 8 interfaces
+ for (MethodNode methodNode : current.getDeclaredMethods("getAt")) {
+ if (methodNode.getParameters().length == 1) {
+ ClassNode paramType = methodNode.getParameters()[0].getType();
+ if (aType.isDerivedFrom(paramType) || aType.declaresInterface(paramType)) {
+ return methodNode;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void writeArrayGet(final Expression receiver, final Expression arguments, final ClassNode rType, final ClassNode aType) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // visit receiver
+ receiver.visit(controller.getAcg());
+ // visit arguments as array index
+ arguments.visit(controller.getAcg());
+ operandStack.doGroovyCast(int_TYPE);
+ int m2 = operandStack.getStackLength();
+ // array access
+ controller.getMethodVisitor().visitInsn(AALOAD);
+ operandStack.replace(rType.getComponentType(), m2 - m1);
+ }
+
+ private void writeOperatorCall(final Expression receiver, final Expression arguments, final String operator) {
+ prepareSiteAndReceiver(receiver, operator, false, controller.getCompileStack().isLHS());
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ visitBoxedArgument(arguments);
+ controller.getOperandStack().doGroovyCast(Number_TYPE);
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/typehandling/NumberMath", operator, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ controller.getOperandStack().replace(Number_TYPE, 2);
+ }
+
+ private void writePowerCall(final Expression receiver, final Expression arguments, final ClassNode rType, final ClassNode aType) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, "power", false, controller.getCompileStack().isLHS());
+ operandStack.doGroovyCast(getWrapper(rType));
+ visitBoxedArgument(arguments);
+ operandStack.doGroovyCast(getWrapper(aType));
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ if (isBigDecimalType(rType) && isWrapperInteger(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigDecimal;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (isBigIntegerType(rType) && isWrapperInteger(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/math/BigInteger;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (isWrapperLong(getWrapper(rType)) && isWrapperInteger(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Long;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else if (isWrapperInteger(getWrapper(rType)) && isWrapperInteger(getWrapper(aType))) {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Integer;Ljava/lang/Integer;)Ljava/lang/Number;", false);
+ } else {
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "power", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ }
+ operandStack.replace(Number_TYPE, m2 - m1);
+ }
+
+ private void writeStringPlusCall(final Expression receiver, final String message, final Expression arguments) {
+ // TODO: performance would be better if we created a StringBuilder
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
+ visitBoxedArgument(arguments);
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/DefaultGroovyMethods", "plus", "(Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/String;", false);
+ operandStack.replace(STRING_TYPE, m2 - m1);
+ }
+
+ private void writeNumberNumberCall(final Expression receiver, final String message, final Expression arguments) {
+ OperandStack operandStack = controller.getOperandStack();
+ int m1 = operandStack.getStackLength();
+ // slow path
+ prepareSiteAndReceiver(receiver, message, false, controller.getCompileStack().isLHS());
+ operandStack.doGroovyCast(Number_TYPE);
+ visitBoxedArgument(arguments);
+ operandStack.doGroovyCast(Number_TYPE);
+ int m2 = operandStack.getStackLength();
+ MethodVisitor mv = controller.getMethodVisitor();
+ mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/dgmimpl/NumberNumber" + capitalize(message), message, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false);
+ operandStack.replace(Number_TYPE, m2 - m1);
+ }
+
+ @Override
+ public void fallbackAttributeOrPropertySite(final PropertyExpression expression, final Expression objectExpression, final String name, final MethodCallerMultiAdapter adapter) {
+ CompileStack compileStack = controller.getCompileStack();
+ OperandStack operandStack = controller.getOperandStack();
+
+ if (name != null && compileStack.isLHS()) {
+ boolean[] isClassReceiver = new boolean[1];
+ ClassNode receiverType = getPropertyOwnerType(objectExpression, isClassReceiver);
+ if (adapter == AsmClassGenerator.setField || adapter == AsmClassGenerator.setGroovyObjectField) {
+ if (setField(expression, objectExpression, receiverType, name)) return;
+ }
+ if (isThisExpression(objectExpression)) {
+ ClassNode classNode = controller.getClassNode();
+ FieldNode fieldNode = receiverType.getField(name);
+ if (fieldNode != null && fieldNode.isPrivate() && !receiverType.equals(classNode)
+ && StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode)) {
+ Map mutators = receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS);
+ if (mutators != null) {
+ MethodNode methodNode = mutators.get(name);
+ if (methodNode != null) {
+ ClassNode rhsType = operandStack.getTopOperand();
+ int i = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
+ VariableSlotLoader rhsValue = new VariableSlotLoader(rhsType, i, operandStack);
+
+ MethodCallExpression call = callX(objectExpression, methodNode.getName(), args(fieldNode.isStatic() ? nullX() : objectExpression, rhsValue));
+ call.setImplicitThis(expression.isImplicitThis());
+ call.setSpreadSafe(expression.isSpreadSafe());
+ call.setSafe(expression.isSafe());
+ call.setMethodTarget(methodNode);
+ call.visit(controller.getAcg());
+
+ // GROOVY-9892: assuming that the mutator method has a return value, make sure the operand
+ // stack is not polluted with the result of the method call
+ operandStack.pop();
+
+ compileStack.removeVar(i);
+ return;
+ }
+ }
+ }
+ }
+ if (isOrImplements(receiverType, MAP_TYPE) && !isClassReceiver[0]) {
+ MethodVisitor mv = controller.getMethodVisitor();
+
+ // store value in temporary variable
+ ClassNode rhsType = operandStack.getTopOperand();
+ int rhs = compileStack.defineTemporaryVariable("$rhs", rhsType, true);
+
+ // push receiver on stack
+ compileStack.pushLHS(false);
+ objectExpression.visit(controller.getAcg());
+ compileStack.popLHS();
+
+ // check if receiver null
+ Label skip = new Label();
+ if (expression.isSafe()) {
+ mv.visitInsn(DUP);
+ mv.visitJumpInsn(IFNULL, skip);
+ }
+
+ mv.visitLdcInsn(name);
+ BytecodeHelper.load(mv, rhsType, rhs);
+ if (isPrimitiveType(rhsType)) BytecodeHelper.doCastToWrappedType(mv, rhsType, getWrapper(rhsType));
+ mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", true);
+
+ if (expression.isSafe()) {
+ mv.visitLabel(skip);
+ }
+ // no return value
+ operandStack.pop();
+ compileStack.removeVar(rhs);
+ return;
+ }
+ }
+
+ super.fallbackAttributeOrPropertySite(expression, objectExpression, name, adapter);
+ }
+
+ private ClassNode getPropertyOwnerType(final Expression receiver, final boolean... isClassReceiver) {
+ Object inferredType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ if (inferredType == null && receiver instanceof VariableExpression) {
+ Variable variable = ((VariableExpression) receiver).getAccessedVariable();
+ if (variable instanceof Expression) {
+ inferredType = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
+ }
+ }
+ ClassNode receiverType;
+ if (inferredType instanceof ClassNode) {
+ // in case a "flow type" is found, it is preferred to use it instead of the declaration type
+ receiverType = (ClassNode) inferredType;
+ } else {
+ receiverType = receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
+ if (receiverType == null) {
+ receiverType = controller.getTypeChooser().resolveType(receiver, controller.getClassNode());
+ }
+ }
+ if (isClassClassNodeWrappingConcreteType(receiverType)) {
+ if (isClassReceiver.length > 0) isClassReceiver[0] = true;
+ receiverType = receiverType.getGenericsTypes()[0].getType();
+ }
+ if (isPrimitiveType(receiverType)) {
+ // GROOVY-6590: wrap primitive types
+ receiverType = getWrapper(receiverType);
+ }
+ return receiverType;
+ }
+
+ // this is just a simple set field handling static and non-static, but not Closure and inner classes
+ private boolean setField(final PropertyExpression expression, final Expression objectExpression, final ClassNode receiverType, final String name) {
+ if (expression.isSafe()) return false;
+ FieldNode fn = AsmClassGenerator.getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(controller.getClassNode(), receiverType, name, false);
+ if (fn == null) return false;
+ OperandStack stack = controller.getOperandStack();
+ stack.doGroovyCast(fn.getType());
+
+ MethodVisitor mv = controller.getMethodVisitor();
+ if (!fn.isStatic()) {
+ controller.getCompileStack().pushLHS(false);
+ objectExpression.visit(controller.getAcg());
+ controller.getCompileStack().popLHS();
+ if (!receiverType.equals(stack.getTopOperand())) {
+ BytecodeHelper.doCast(mv, receiverType);
+ stack.replace(receiverType);
+ }
+ stack.swap();
+ mv.visitFieldInsn(PUTFIELD, BytecodeHelper.getClassInternalName(fn.getOwner()), name, BytecodeHelper.getTypeDescription(fn.getType()));
+ stack.remove(1);
+ } else {
+ mv.visitFieldInsn(PUTSTATIC, BytecodeHelper.getClassInternalName(receiverType), name, BytecodeHelper.getTypeDescription(fn.getType()));
+ }
+
+ return true;
+ }
+
+ /*private boolean getField(final PropertyExpression expression, final Expression receiver, ClassNode receiverType, final String name) {
+ boolean safe = expression.isSafe();
+ boolean implicitThis = expression.isImplicitThis();
+
+ if (makeGetField(receiver, receiverType, name, safe, implicitThis)) return true;
+ if (receiver instanceof ClassExpression) {
+ if (makeGetField(receiver, receiver.getType(), name, safe, implicitThis)) return true;
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), name, safe, implicitThis)) return true;
+ }
+ if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, name, safe, implicitThis)) return true;
+
+ boolean isClassReceiver = false;
+ if (isClassClassNodeWrappingConcreteType(receiverType)) {
+ isClassReceiver = true;
+ receiverType = receiverType.getGenericsTypes()[0].getType();
+ }
+ if (isClassReceiver && makeGetField(receiver, CLASS_Type, name, safe, false)) return true;
+ if (receiverType.isEnum()) {
+ controller.getMethodVisitor().visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), name, BytecodeHelper.getTypeDescription(receiverType));
+ controller.getOperandStack().push(receiverType);
+ return true;
+ }
+ return false;
+ }*/
+
+ private void addPropertyAccessError(final Expression receiver, final String propertyName, final ClassNode receiverType) {
+ String receiverName = (receiver instanceof ClassExpression ? receiver.getType() : receiverType).toString(false);
+ String message = "Access to " + receiverName + "#" + propertyName + " is forbidden";
+ controller.getSourceUnit().addError(new SyntaxException(message, receiver));
+ }
+}