/** * 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. * * @author Cedric Champeau */ public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes { private static final ClassNode INVOKERHELPER_TYPE = ClassHelper.make(InvokerHelper.class); 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 static final ClassNode COLLECTION_TYPE = make(Collection.class); private static final MethodNode COLLECTION_SIZE_METHOD = COLLECTION_TYPE.getMethod("size", Parameter.EMPTY_ARRAY); private static final MethodNode MAP_GET_METHOD = MAP_TYPE.getMethod("get", new Parameter[] {new Parameter(OBJECT_TYPE, "key")}); private WriterController controller; public StaticTypesCallSiteWriter(final StaticTypesWriterController controller) { super(controller); this.controller = controller; } @Override public void generateCallSiteArray() { if (controller instanceof StaticTypesWriterController) { ((StaticTypesWriterController) controller).getRegularCallSiteWriter().generateCallSiteArray(); } else { super.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( Expression receiver, final String methodName, final boolean safe, final boolean implicitThis) { Object dynamic = receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY); if (dynamic != null) { MethodNode target = safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD; MethodCallExpression mce = new MethodCallExpression( new ClassExpression(INVOKERHELPER_TYPE), target.getName(), new ArgumentListExpression(receiver, new ConstantExpression(methodName))); mce.setSafe(false); mce.setImplicitThis(false); mce.setMethodTarget(target); mce.visit(controller.getAcg()); return; } TypeChooser typeChooser = controller.getTypeChooser(); ClassNode classNode = controller.getClassNode(); ClassNode receiverType = (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER); if (receiverType == null) { receiverType = typeChooser.resolveType(receiver, classNode); } Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); if (type == null && receiver instanceof VariableExpression) { Variable variable = ((VariableExpression) receiver).getAccessedVariable(); if (variable instanceof Expression) { type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); } } if (type != null) { // in case a "flow type" is found, it is preferred to use it instead of // the declaration type receiverType = (ClassNode) type; } boolean isClassReceiver = false; if (isClassClassNodeWrappingConcreteType(receiverType)) { isClassReceiver = true; receiverType = receiverType.getGenericsTypes()[0].getType(); } MethodVisitor mv = controller.getMethodVisitor(); if (receiverType.isArray() && methodName.equals("length")) { receiver.visit(controller.getAcg()); ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode); controller.getOperandStack().doGroovyCast(arrayGetReturnType); mv.visitInsn(ARRAYLENGTH); controller.getOperandStack().replace(int_TYPE); return; } else if ((receiverType.implementsInterface(COLLECTION_TYPE) || COLLECTION_TYPE.equals(receiverType)) && ("size".equals(methodName) || "length".equals(methodName))) { MethodCallExpression expr = new MethodCallExpression(receiver, "size", ArgumentListExpression.EMPTY_ARGUMENTS); expr.setMethodTarget(COLLECTION_SIZE_METHOD); expr.setImplicitThis(implicitThis); expr.setSafe(safe); expr.visit(controller.getAcg()); return; } if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return; if (makeGetField( receiver, receiverType, methodName, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return; if (receiverType.isEnum()) { mv.visitFieldInsn( GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), methodName, BytecodeHelper.getTypeDescription(receiverType)); controller.getOperandStack().push(receiverType); return; } if (receiver instanceof ClassExpression) { if (makeGetField( receiver, receiver.getType(), methodName, implicitThis, samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return; if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis)) return; if (makeGetPrivateFieldWithBridgeMethod( receiver, receiver.getType(), methodName, safe, implicitThis)) return; } if (isClassReceiver) { // we are probably looking for a property of the class if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return; if (makeGetField(receiver, CLASS_Type, methodName, false, true)) return; } if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis)) return; // GROOVY-5580, it is still possible that we're calling a superinterface property String getterName = "get" + MetaClassHelper.capitalize(methodName); if (receiverType.isInterface()) { Set<ClassNode> allInterfaces = receiverType.getAllInterfaces(); MethodNode getterMethod = null; for (ClassNode anInterface : allInterfaces) { getterMethod = anInterface.getGetterMethod(getterName); if (getterMethod != null) break; } // GROOVY-5585 if (getterMethod == null) { getterMethod = OBJECT_TYPE.getGetterMethod(getterName); } if (getterMethod != null) { MethodCallExpression call = new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS); call.setMethodTarget(getterMethod); call.setImplicitThis(false); 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<MethodNode> methods = findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), receiverType, getterName, ClassNode.EMPTY_ARRAY); if (!methods.isEmpty()) { List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY); if (methodNodes.size() == 1) { MethodNode getter = methodNodes.get(0); MethodCallExpression call = new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS); call.setMethodTarget(getter); call.setImplicitThis(false); call.setSourcePosition(receiver); call.visit(controller.getAcg()); return; } } boolean isStaticProperty = receiver instanceof ClassExpression && (receiverType.isDerivedFrom(receiver.getType()) || receiverType.implementsInterface(receiver.getType())); if (!isStaticProperty) { if (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType)) { // for maps, replace map.foo with map.get('foo') writeMapDotProperty(receiver, methodName, mv, safe); return; } if (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType)) { writeListDotProperty(receiver, methodName, mv, safe); return; } } controller .getSourceUnit() .addError( new SyntaxException( "Access to " + (receiver instanceof ClassExpression ? receiver.getType() : receiverType) .toString(false) + "#" + methodName + " is forbidden", receiver.getLineNumber(), receiver.getColumnNumber(), receiver.getLastLineNumber(), receiver.getLastColumnNumber())); controller.getMethodVisitor().visitInsn(ACONST_NULL); controller.getOperandStack().push(OBJECT_TYPE); } private void writeMapDotProperty( final Expression receiver, final String methodName, final MethodVisitor mv, final boolean safe) { receiver.visit(controller.getAcg()); // load receiver 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(methodName); // 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 methodName, final MethodVisitor mv, final boolean safe) { ClassNode componentType = (ClassNode) 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(); 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 = new VariableExpression("tmpList", make(ArrayList.class)); int var = compileStack.defineTemporaryVariable(tmpList, false); Variable iterator = new VariableExpression("iterator", Iterator_TYPE); int it = compileStack.defineTemporaryVariable(iterator, false); Variable nextVar = new VariableExpression("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", "<init>", "(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); final ClassNode finalComponentType = componentType; PropertyExpression pexp = new PropertyExpression( new BytecodeExpression() { @Override public void visit(final MethodVisitor mv) { mv.visitVarInsn(ALOAD, next); } @Override public ClassNode getType() { return finalComponentType; } }, methodName); 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(make(ArrayList.class)); controller.getCompileStack().removeVar(next); controller.getCompileStack().removeVar(it); controller.getCompileStack().removeVar(var); } @SuppressWarnings("unchecked") private boolean makeGetPrivateFieldWithBridgeMethod( final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean safe, final boolean implicitThis) { FieldNode field = receiverType.getField(fieldName); ClassNode classNode = controller.getClassNode(); if (field != null && Modifier.isPrivate(field.getModifiers()) && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode) || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode, receiverType)) && !receiverType.equals(classNode)) { Map<String, MethodNode> accessors = (Map<String, MethodNode>) receiverType .redirect() .getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_ACCESSORS); if (accessors != null) { MethodNode methodNode = accessors.get(fieldName); if (methodNode != null) { MethodCallExpression mce = new MethodCallExpression( receiver, methodNode.getName(), new ArgumentListExpression( field.isStatic() ? new ConstantExpression(null) : receiver)); mce.setMethodTarget(methodNode); mce.setSafe(safe); mce.setImplicitThis(implicitThis); mce.visit(controller.getAcg()); return true; } } } return false; } @Override public void makeGroovyObjectGetPropertySite( final Expression receiver, final String methodName, final boolean safe, final boolean implicitThis) { TypeChooser typeChooser = controller.getTypeChooser(); ClassNode classNode = controller.getClassNode(); ClassNode receiverType = typeChooser.resolveType(receiver, classNode); if (receiver instanceof VariableExpression && ((VariableExpression) receiver).isThisExpression() && !controller.isInClosure()) { receiverType = classNode; } String property = methodName; if (implicitThis) { if (controller.getInvocationWriter() instanceof StaticInvocationWriter) { MethodCallExpression currentCall = ((StaticInvocationWriter) controller.getInvocationWriter()).getCurrentCall(); if (currentCall != null && currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER) != null) { property = (String) currentCall.getNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER); String[] props = property.split("\\."); BytecodeExpression thisLoader = new BytecodeExpression() { @Override public void visit(final MethodVisitor mv) { mv.visitVarInsn(ALOAD, 0); // load this } }; thisLoader.setType(CLOSURE_TYPE); Expression pexp = new PropertyExpression(thisLoader, new ConstantExpression(props[0]), safe); for (int i = 1, propsLength = props.length; i < propsLength; i++) { final String prop = props[i]; pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, CLOSURE_TYPE); pexp = new PropertyExpression(pexp, prop); } pexp.visit(controller.getAcg()); return; } } } if (makeGetPropertyWithGetter(receiver, receiverType, property, safe, implicitThis)) return; if (makeGetField( receiver, receiverType, property, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return; MethodCallExpression call = new MethodCallExpression( receiver, "getProperty", new ArgumentListExpression(new ConstantExpression(property))); call.setImplicitThis(implicitThis); call.setSafe(safe); call.setMethodTarget(GROOVYOBJECT_GETPROPERTY_METHOD); call.visit(controller.getAcg()); return; } @Override public void makeCallSiteArrayInitializer() {} private boolean makeGetPropertyWithGetter( final Expression receiver, final ClassNode receiverType, final String methodName, final boolean safe, final boolean implicitThis) { // does a getter exists ? String getterName = "get" + MetaClassHelper.capitalize(methodName); MethodNode getterNode = receiverType.getGetterMethod(getterName); if (getterNode == null) { getterName = "is" + MetaClassHelper.capitalize(methodName); 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(methodName); if (propertyNode != null) { // it is possible to use a getter String prefix = "get"; if (boolean_TYPE.equals(propertyNode.getOriginType())) { prefix = "is"; } getterName = prefix + MetaClassHelper.capitalize(methodName); 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 = new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS); call.setSourcePosition(receiver); call.setMethodTarget(getterNode); call.setImplicitThis(implicitThis); call.setSafe(safe); call.visit(controller.getAcg()); return true; } if (receiverType instanceof InnerClassNode && !receiverType.isStaticClass()) { if (makeGetPropertyWithGetter( receiver, receiverType.getOuterClass(), methodName, safe, implicitThis)) { return true; } } // go upper level ClassNode superClass = receiverType.getSuperClass(); if (superClass != null) { return makeGetPropertyWithGetter(receiver, superClass, methodName, safe, implicitThis); } return false; } boolean makeGetField( final Expression receiver, final ClassNode receiverType, final String fieldName, final boolean implicitThis, final boolean samePackage) { FieldNode field = receiverType.getField(fieldName); // direct access is allowed if we are in the same class as the declaring class // or we are in an inner class if (field != null && isDirectAccessAllowed(field, controller.getClassNode(), samePackage)) { CompileStack compileStack = controller.getCompileStack(); MethodVisitor mv = controller.getMethodVisitor(); if (field.isStatic()) { mv.visitFieldInsn( GETSTATIC, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(field.getOriginType())); controller.getOperandStack().push(field.getOriginType()); } else { if (implicitThis) { compileStack.pushImplicitThis(implicitThis); } receiver.visit(controller.getAcg()); if (implicitThis) compileStack.popImplicitThis(); if (!controller.getOperandStack().getTopOperand().isDerivedFrom(field.getOwner())) { mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner())); } mv.visitFieldInsn( GETFIELD, BytecodeHelper.getClassInternalName(field.getOwner()), fieldName, BytecodeHelper.getTypeDescription(field.getOriginType())); } controller.getOperandStack().replace(field.getOriginType()); return true; } ClassNode superClass = receiverType.getSuperClass(); if (superClass != null) { return makeGetField(receiver, superClass, fieldName, implicitThis, false); } return false; } private static boolean samePackages(final String pkg1, final String pkg2) { return ((pkg1 == null && pkg2 == null) || pkg1 != null && pkg1.equals(pkg2)); } private static boolean isDirectAccessAllowed( FieldNode a, ClassNode receiver, boolean isSamePackage) { ClassNode declaringClass = a.getDeclaringClass().redirect(); ClassNode receiverType = receiver.redirect(); // first, direct access from within the class or inner class nodes if (declaringClass.equals(receiverType)) return true; if (receiverType instanceof InnerClassNode) { while (receiverType != null && receiverType instanceof InnerClassNode) { if (declaringClass.equals(receiverType)) return true; receiverType = receiverType.getOuterClass(); } } // no getter return a.isPublic() || (a.isProtected() && isSamePackage); } @Override public void makeSiteEntry() {} @Override public void prepareCallSite(final String message) {} @Override public void makeSingleArgumentCall( final Expression receiver, final String message, final Expression arguments) { 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)) { return; } // new try with flow type instead of declaration type rType = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE); if (rType != null && trySubscript(receiver, message, arguments, rType, aType)) { 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 this error and file" + "a bug report at http://jira.codehaus.org/browse/GROOVY"); } private boolean trySubscript( final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType) { 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)) { writeModCall(receiver, arguments, rType, aType); 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)) { 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.getMethod("getAt", new Parameter[] {new Parameter(aType, "index")}); if (getAtNode == null && isPrimitiveType(aType)) { getAtNode = current.getMethod( "getAt", new Parameter[] {new Parameter(getWrapper(aType), "index")}); } else if (getAtNode == null && aType.isDerivedFrom(Number_TYPE)) { getAtNode = current.getMethod( "getAt", new Parameter[] {new Parameter(getUnwrapper(aType), "index")}); } current = current.getSuperClass(); } if (getAtNode != null) { MethodCallExpression call = new MethodCallExpression(receiver, "getAt", arguments); call.setSourcePosition(arguments); call.setImplicitThis(false); call.setMethodTarget(getAtNode); 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<MethodNode> nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), rType, message, args); if (nodes.isEmpty()) { // retry with raw types rType = rType.getPlainNodeReference(); nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), rType, message, args); } nodes = StaticTypeCheckingSupport.chooseBestMethod(rType, nodes, args); if (nodes.size() == 1 || nodes.size() > 1 && acceptAnyMethod) { MethodNode methodNode = nodes.get(0); MethodCallExpression call = new MethodCallExpression(receiver, message, arguments); call.setSourcePosition(arguments); call.setImplicitThis(false); call.setMethodTarget(methodNode); call.visit(controller.getAcg()); return true; } if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) { // fallback to Map#get MethodCallExpression call = new MethodCallExpression(receiver, "get", arguments); call.setMethodTarget(MAP_GET_METHOD); call.setSourcePosition(arguments); call.setImplicitThis(false); call.visit(controller.getAcg()); return true; } } } return false; } 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 writeModCall( Expression receiver, Expression arguments, ClassNode rType, ClassNode aType) { prepareSiteAndReceiver(receiver, "mod", 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", "mod", "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false); controller.getOperandStack().replace(Number_TYPE, 2); } private void writePowerCall( Expression receiver, Expression arguments, final ClassNode rType, 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" + MetaClassHelper.capitalize(message), message, "(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;", false); controller.getOperandStack().replace(Number_TYPE, m2 - m1); } }
private boolean trySubscript( final Expression receiver, final String message, final Expression arguments, ClassNode rType, final ClassNode aType) { 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)) { writeModCall(receiver, arguments, rType, aType); 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)) { 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.getMethod("getAt", new Parameter[] {new Parameter(aType, "index")}); if (getAtNode == null && isPrimitiveType(aType)) { getAtNode = current.getMethod( "getAt", new Parameter[] {new Parameter(getWrapper(aType), "index")}); } else if (getAtNode == null && aType.isDerivedFrom(Number_TYPE)) { getAtNode = current.getMethod( "getAt", new Parameter[] {new Parameter(getUnwrapper(aType), "index")}); } current = current.getSuperClass(); } if (getAtNode != null) { MethodCallExpression call = new MethodCallExpression(receiver, "getAt", arguments); call.setSourcePosition(arguments); call.setImplicitThis(false); call.setMethodTarget(getAtNode); 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<MethodNode> nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), rType, message, args); if (nodes.isEmpty()) { // retry with raw types rType = rType.getPlainNodeReference(); nodes = StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments( controller.getSourceUnit().getClassLoader(), rType, message, args); } nodes = StaticTypeCheckingSupport.chooseBestMethod(rType, nodes, args); if (nodes.size() == 1 || nodes.size() > 1 && acceptAnyMethod) { MethodNode methodNode = nodes.get(0); MethodCallExpression call = new MethodCallExpression(receiver, message, arguments); call.setSourcePosition(arguments); call.setImplicitThis(false); call.setMethodTarget(methodNode); call.visit(controller.getAcg()); return true; } if (implementsInterfaceOrIsSubclassOf(rType, MAP_TYPE)) { // fallback to Map#get MethodCallExpression call = new MethodCallExpression(receiver, "get", arguments); call.setMethodTarget(MAP_GET_METHOD); call.setSourcePosition(arguments); call.setImplicitThis(false); call.visit(controller.getAcg()); return true; } } } return false; }
/** * This visitor is responsible for amending the AST with static compilation metadata or transform * the AST so that a class or a method can be statically compiled. It may also throw errors specific * to static compilation which are not considered as an error at the type check pass. For example, * usage of spread operator is not allowed in statically compiled portions of code, while it may be * statically checked. * * <p>Static compilation relies on static type checking, which explains why this visitor extends the * type checker visitor. * * @author Cedric Champeau */ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor { private static final ClassNode TYPECHECKED_CLASSNODE = ClassHelper.make(TypeChecked.class); private static final ClassNode COMPILESTATIC_CLASSNODE = ClassHelper.make(CompileStatic.class); private static final ClassNode[] TYPECHECKED_ANNOTATIONS = { TYPECHECKED_CLASSNODE, COMPILESTATIC_CLASSNODE }; public static final ClassNode ARRAYLIST_CLASSNODE = ClassHelper.make(ArrayList.class); public static final MethodNode ARRAYLIST_CONSTRUCTOR; public static final MethodNode ARRAYLIST_ADD_METHOD = ARRAYLIST_CLASSNODE.getMethod( "add", new Parameter[] {new Parameter(ClassHelper.OBJECT_TYPE, "o")}); static { ARRAYLIST_CONSTRUCTOR = new ConstructorNode( ACC_PUBLIC, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, EmptyStatement.INSTANCE); ARRAYLIST_CONSTRUCTOR.setDeclaringClass(StaticCompilationVisitor.ARRAYLIST_CLASSNODE); } private final TypeChooser typeChooser = new StaticTypesTypeChooser(); private ClassNode classNode; public StaticCompilationVisitor(final SourceUnit unit, final ClassNode node) { super(unit, node); } @Override protected ClassNode[] getTypeCheckingAnnotations() { return TYPECHECKED_ANNOTATIONS; } public static boolean isStaticallyCompiled(AnnotatedNode node) { if (node.getNodeMetaData(STATIC_COMPILE_NODE) != null) return (Boolean) node.getNodeMetaData(STATIC_COMPILE_NODE); if (node instanceof MethodNode) { return isStaticallyCompiled(node.getDeclaringClass()); } if (node instanceof InnerClassNode) { return isStaticallyCompiled(((InnerClassNode) node).getOuterClass()); } return false; } private void addPrivateFieldAndMethodAccessors(ClassNode node) { addPrivateBridgeMethods(node); addPrivateFieldsAccessors(node); Iterator<InnerClassNode> it = node.getInnerClasses(); while (it.hasNext()) { addPrivateFieldAndMethodAccessors(it.next()); } } @Override public void visitClass(final ClassNode node) { boolean skip = shouldSkipClassNode(node); if (!skip && !anyMethodSkip(node)) { node.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY); } ClassNode oldCN = classNode; classNode = node; Iterator<InnerClassNode> innerClasses = classNode.getInnerClasses(); while (innerClasses.hasNext()) { InnerClassNode innerClassNode = innerClasses.next(); boolean innerStaticCompile = !(skip || isSkippedInnerClass(innerClassNode)); innerClassNode.putNodeMetaData(STATIC_COMPILE_NODE, innerStaticCompile); innerClassNode.putNodeMetaData( WriterControllerFactory.class, node.getNodeMetaData(WriterControllerFactory.class)); if (innerStaticCompile && !anyMethodSkip(innerClassNode)) { innerClassNode.putNodeMetaData(MopWriter.Factory.class, StaticCompilationMopWriter.FACTORY); } } super.visitClass(node); addPrivateFieldAndMethodAccessors(node); classNode = oldCN; } private boolean anyMethodSkip(final ClassNode node) { for (MethodNode methodNode : node.getMethods()) { if (isSkipMode(methodNode)) return true; } return false; } /** * If we are in a constructor, that is static compiled, but in a class, that is not, it may happen * that init code from object initializers, fields or properties is added into the constructor * code. The backend assumes a purely static contructor, so it may fail if it encounters dynamic * code here. Thus we make this kind of code fail */ private void checkForConstructorWithCSButClassWithout(MethodNode node) { if (!(node instanceof ConstructorNode)) return; Object meta = node.getNodeMetaData(STATIC_COMPILE_NODE); if (!Boolean.TRUE.equals(meta)) return; ClassNode clz = typeCheckingContext.getEnclosingClassNode(); meta = clz.getNodeMetaData(STATIC_COMPILE_NODE); if (Boolean.TRUE.equals(meta)) return; if (clz.getObjectInitializerStatements().isEmpty() && clz.getFields().isEmpty() && clz.getProperties().isEmpty()) { return; } addStaticTypeError( "Cannot statically compile constructor implicitly including non static elements from object initializers, properties or fields.", node); } @Override public void visitMethod(final MethodNode node) { if (isSkipMode(node)) { node.putNodeMetaData(STATIC_COMPILE_NODE, false); } super.visitMethod(node); checkForConstructorWithCSButClassWithout(node); } /** Adds special accessors for private constants so that inner classes can retrieve them. */ @SuppressWarnings("unchecked") private void addPrivateFieldsAccessors(ClassNode node) { Set<ASTNode> accessedFields = (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS); if (accessedFields == null) return; Map<String, MethodNode> privateConstantAccessors = (Map<String, MethodNode>) node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS); if (privateConstantAccessors != null) { // already added return; } int acc = -1; privateConstantAccessors = new HashMap<String, MethodNode>(); final int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC; for (FieldNode fieldNode : node.getFields()) { if (accessedFields.contains(fieldNode)) { acc++; Parameter param = new Parameter(node.getPlainNodeReference(), "$that"); Expression receiver = fieldNode.isStatic() ? new ClassExpression(node) : new VariableExpression(param); Statement stmt = new ExpressionStatement(new PropertyExpression(receiver, fieldNode.getName())); MethodNode accessor = node.addMethod( "pfaccess$" + acc, access, fieldNode.getOriginType(), new Parameter[] {param}, ClassNode.EMPTY_ARRAY, stmt); privateConstantAccessors.put(fieldNode.getName(), accessor); } } node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateConstantAccessors); } /** * This method is used to add "bridge" methods for private methods of an inner/outer class, so * that the outer class is capable of calling them. It does basically the same job as access$000 * like methods in Java. * * @param node an inner/outer class node for which to generate bridge methods */ @SuppressWarnings("unchecked") private void addPrivateBridgeMethods(final ClassNode node) { Set<ASTNode> accessedMethods = (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_METHODS_ACCESS); if (accessedMethods == null) return; List<MethodNode> methods = new ArrayList<MethodNode>(node.getAllDeclaredMethods()); Map<MethodNode, MethodNode> privateBridgeMethods = (Map<MethodNode, MethodNode>) node.getNodeMetaData(PRIVATE_BRIDGE_METHODS); if (privateBridgeMethods != null) { // private bridge methods already added return; } privateBridgeMethods = new HashMap<MethodNode, MethodNode>(); int i = -1; final int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC; for (MethodNode method : methods) { if (accessedMethods.contains(method)) { i++; Parameter[] methodParameters = method.getParameters(); Parameter[] newParams = new Parameter[methodParameters.length + 1]; System.arraycopy(methodParameters, 0, newParams, 1, methodParameters.length); newParams[0] = new Parameter(node.getPlainNodeReference(), "$that"); Expression arguments; if (method.getParameters() == null || method.getParameters().length == 0) { arguments = ArgumentListExpression.EMPTY_ARGUMENTS; } else { List<Expression> args = new LinkedList<Expression>(); for (Parameter parameter : methodParameters) { args.add(new VariableExpression(parameter)); } arguments = new ArgumentListExpression(args); } Expression receiver = method.isStatic() ? new ClassExpression(node) : new VariableExpression(newParams[0]); MethodCallExpression mce = new MethodCallExpression(receiver, method.getName(), arguments); mce.setMethodTarget(method); ExpressionStatement returnStatement = new ExpressionStatement(mce); MethodNode bridge = node.addMethod( "access$" + i, access, method.getReturnType(), newParams, method.getExceptions(), returnStatement); privateBridgeMethods.put(method, bridge); bridge.addAnnotation(new AnnotationNode(COMPILESTATIC_CLASSNODE)); } } if (!privateBridgeMethods.isEmpty()) { node.setNodeMetaData(PRIVATE_BRIDGE_METHODS, privateBridgeMethods); } } private void memorizeInitialExpressions(final MethodNode node) { // add node metadata for default parameters because they are erased by the Verifier if (node.getParameters() != null) { for (Parameter parameter : node.getParameters()) { parameter.putNodeMetaData( StaticTypesMarker.INITIAL_EXPRESSION, parameter.getInitialExpression()); } } } @Override public void visitSpreadExpression(final SpreadExpression expression) {} @Override public void visitMethodCallExpression(final MethodCallExpression call) { super.visitMethodCallExpression(call); MethodNode target = (MethodNode) call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); if (target != null) { call.setMethodTarget(target); memorizeInitialExpressions(target); } if (call.getMethodTarget() == null && call.getLineNumber() > 0) { addError("Target method for method call expression hasn't been set", call); } } @Override public void visitConstructorCallExpression(final ConstructorCallExpression call) { super.visitConstructorCallExpression(call); MethodNode target = (MethodNode) call.getNodeMetaData(DIRECT_METHOD_CALL_TARGET); if (target == null && call.getLineNumber() > 0) { addError("Target constructor for constructor call expression hasn't been set", call); } else { if (target == null) { // try to find a target ArgumentListExpression argumentListExpression = InvocationWriter.makeArgumentList(call.getArguments()); List<Expression> expressions = argumentListExpression.getExpressions(); ClassNode[] args = new ClassNode[expressions.size()]; for (int i = 0; i < args.length; i++) { args[i] = typeChooser.resolveType(expressions.get(i), classNode); } MethodNode constructor = findMethodOrFail( call, call.isSuperCall() ? classNode.getSuperClass() : classNode, "<init>", args); call.putNodeMetaData(DIRECT_METHOD_CALL_TARGET, constructor); target = constructor; } } if (target != null) { memorizeInitialExpressions(target); } } @Override public void visitForLoop(final ForStatement forLoop) { super.visitForLoop(forLoop); Expression collectionExpression = forLoop.getCollectionExpression(); if (!(collectionExpression instanceof ClosureListExpression)) { final ClassNode collectionType = getType(forLoop.getCollectionExpression()); ClassNode componentType = inferLoopElementType(collectionType); forLoop.getVariable().setType(componentType); forLoop.getVariable().setOriginType(componentType); } } @Override protected MethodNode findMethodOrFail( final Expression expr, final ClassNode receiver, final String name, final ClassNode... args) { MethodNode methodNode = super.findMethodOrFail(expr, receiver, name, args); if (expr instanceof BinaryExpression && methodNode != null) { expr.putNodeMetaData(BINARY_EXP_TARGET, new Object[] {methodNode, name}); } return methodNode; } @Override protected boolean existsProperty( final PropertyExpression pexp, final boolean checkForReadOnly, final ClassCodeVisitorSupport visitor) { Expression objectExpression = pexp.getObjectExpression(); ClassNode objectExpressionType = getType(objectExpression); final Reference<ClassNode> rType = new Reference<ClassNode>(objectExpressionType); ClassCodeVisitorSupport receiverMemoizer = new ClassCodeVisitorSupport() { @Override protected SourceUnit getSourceUnit() { return null; } public void visitField(final FieldNode node) { if (visitor != null) visitor.visitField(node); ClassNode declaringClass = node.getDeclaringClass(); if (declaringClass != null) rType.set(declaringClass); } public void visitMethod(final MethodNode node) { if (visitor != null) visitor.visitMethod(node); ClassNode declaringClass = node.getDeclaringClass(); if (declaringClass != null) rType.set(declaringClass); } @Override public void visitProperty(final PropertyNode node) { if (visitor != null) visitor.visitProperty(node); ClassNode declaringClass = node.getDeclaringClass(); if (declaringClass != null) rType.set(declaringClass); } }; boolean exists = super.existsProperty(pexp, checkForReadOnly, receiverMemoizer); if (exists) { if (objectExpression.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER) == null) { objectExpression.putNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER, rType.get()); } if (StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf( objectExpressionType, ClassHelper.LIST_TYPE)) { objectExpression.putNodeMetaData( COMPONENT_TYPE, inferComponentType(objectExpressionType, ClassHelper.int_TYPE)); } } return exists; } @Override public void visitPropertyExpression(final PropertyExpression pexp) { super.visitPropertyExpression(pexp); Object dynamic = pexp.getNodeMetaData(StaticTypesMarker.DYNAMIC_RESOLUTION); if (dynamic != null) { pexp.getObjectExpression() .putNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY, dynamic); } } }