Java tutorial
/* * 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; import groovy.lang.GroovyRuntimeException; import org.apache.groovy.io.StringBuilderWriter; import org.codehaus.groovy.GroovyBugError; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.AnnotatedNode; import org.codehaus.groovy.ast.AnnotationNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CompileUnit; import org.codehaus.groovy.ast.ConstructorNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.GenericsType; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.InterfaceHelperClassNode; import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.ast.PackageNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.PropertyNode; import org.codehaus.groovy.ast.expr.AnnotationConstantExpression; import org.codehaus.groovy.ast.expr.ArgumentListExpression; import org.codehaus.groovy.ast.expr.ArrayExpression; import org.codehaus.groovy.ast.expr.AttributeExpression; import org.codehaus.groovy.ast.expr.BinaryExpression; import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; import org.codehaus.groovy.ast.expr.BooleanExpression; import org.codehaus.groovy.ast.expr.CastExpression; import org.codehaus.groovy.ast.expr.ClassExpression; import org.codehaus.groovy.ast.expr.ClosureExpression; import org.codehaus.groovy.ast.expr.ClosureListExpression; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.ConstructorCallExpression; import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.EmptyExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.GStringExpression; import org.codehaus.groovy.ast.expr.LambdaExpression; import org.codehaus.groovy.ast.expr.ListExpression; import org.codehaus.groovy.ast.expr.MapEntryExpression; import org.codehaus.groovy.ast.expr.MapExpression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.MethodPointerExpression; import org.codehaus.groovy.ast.expr.MethodReferenceExpression; import org.codehaus.groovy.ast.expr.NotExpression; import org.codehaus.groovy.ast.expr.PostfixExpression; import org.codehaus.groovy.ast.expr.PrefixExpression; import org.codehaus.groovy.ast.expr.PropertyExpression; import org.codehaus.groovy.ast.expr.RangeExpression; import org.codehaus.groovy.ast.expr.SpreadExpression; import org.codehaus.groovy.ast.expr.SpreadMapExpression; import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; import org.codehaus.groovy.ast.expr.TernaryExpression; import org.codehaus.groovy.ast.expr.TupleExpression; import org.codehaus.groovy.ast.expr.UnaryMinusExpression; import org.codehaus.groovy.ast.expr.UnaryPlusExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.groovy.ast.stmt.AssertStatement; import org.codehaus.groovy.ast.stmt.BlockStatement; import org.codehaus.groovy.ast.stmt.BreakStatement; import org.codehaus.groovy.ast.stmt.CaseStatement; import org.codehaus.groovy.ast.stmt.CatchStatement; import org.codehaus.groovy.ast.stmt.ContinueStatement; import org.codehaus.groovy.ast.stmt.DoWhileStatement; import org.codehaus.groovy.ast.stmt.ExpressionStatement; import org.codehaus.groovy.ast.stmt.ForStatement; import org.codehaus.groovy.ast.stmt.IfStatement; import org.codehaus.groovy.ast.stmt.ReturnStatement; import org.codehaus.groovy.ast.stmt.Statement; import org.codehaus.groovy.ast.stmt.SwitchStatement; import org.codehaus.groovy.ast.stmt.SynchronizedStatement; import org.codehaus.groovy.ast.stmt.ThrowStatement; import org.codehaus.groovy.ast.stmt.TryCatchStatement; import org.codehaus.groovy.ast.stmt.WhileStatement; import org.codehaus.groovy.ast.tools.WideningCategories; import org.codehaus.groovy.classgen.asm.BytecodeHelper; import org.codehaus.groovy.classgen.asm.BytecodeVariable; import org.codehaus.groovy.classgen.asm.MethodCaller; import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter; import org.codehaus.groovy.classgen.asm.MopWriter; import org.codehaus.groovy.classgen.asm.OperandStack; import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter; import org.codehaus.groovy.classgen.asm.WriterController; import org.codehaus.groovy.classgen.asm.WriterControllerFactory; import org.codehaus.groovy.control.SourceUnit; import org.codehaus.groovy.runtime.ScriptBytecodeAdapter; import org.codehaus.groovy.syntax.RuntimeParserException; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.objectweb.asm.util.TraceMethodVisitor; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Objects; import static org.apache.groovy.util.BeanUtils.capitalize; /** * Generates Java class versions of Groovy classes using ASM. */ public class AsmClassGenerator extends ClassGenerator { // fields public static final MethodCallerMultiAdapter setField = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "setField", false, false); public static final MethodCallerMultiAdapter getField = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "getField", false, false); //private static final MethodCallerMultiAdapter setFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setFieldOnSuper", false, false); //private static final MethodCallerMultiAdapter getFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getFieldOnSuper", false, false); public static final MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectField", false, false); public static final MethodCallerMultiAdapter getGroovyObjectField = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectField", false, false); // properties public static final MethodCallerMultiAdapter setProperty = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "setProperty", false, false); private static final MethodCallerMultiAdapter getProperty = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "getProperty", false, false); //private static final MethodCallerMultiAdapter setPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setPropertyOnSuper", false, false); //private static final MethodCallerMultiAdapter getPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getPropertyOnSuper", false, false); private static final MethodCallerMultiAdapter setGroovyObjectProperty = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectProperty", false, false); private static final MethodCallerMultiAdapter getGroovyObjectProperty = MethodCallerMultiAdapter .newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectProperty", false, false); // spread expressions private static final MethodCaller spreadMap = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "spreadMap"); private static final MethodCaller despreadList = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "despreadList"); // type conversions private static final MethodCaller createMapMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createMap"); private static final MethodCaller createListMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createList"); private static final MethodCaller createRangeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createRange"); private static final MethodCaller createPojoWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createPojoWrapper"); private static final MethodCaller createGroovyObjectWrapperMethod = MethodCaller .newStatic(ScriptBytecodeAdapter.class, "createGroovyObjectWrapper"); private final Map<String, GenericsType> genericParameterNames = new HashMap<>(); private final Map<String, ClassNode> referencedClasses = new HashMap<>(); private boolean passingParams; public static final boolean CREATE_DEBUG_INFO = true; public static final boolean CREATE_LINE_NUMBER_INFO = true; public static final boolean ASM_DEBUG = false; // add marker in the bytecode to show source-bytecode relationship public static final String MINIMUM_BYTECODE_VERSION = "_MINIMUM_BYTECODE_VERSION"; private WriterController controller; private ASTNode currentASTNode; private final SourceUnit source; private final GeneratorContext context; private ClassVisitor classVisitor; private final String sourceFile; public AsmClassGenerator(final SourceUnit source, final GeneratorContext context, final ClassVisitor classVisitor, final String sourceFile) { this.source = source; this.context = context; this.classVisitor = classVisitor; this.sourceFile = sourceFile; } @Override public SourceUnit getSourceUnit() { return source; } public WriterController getController() { return controller; } // GroovyClassVisitor interface //-------------------------------------------------------------------------- @Override public void visitClass(final ClassNode classNode) { referencedClasses.clear(); WriterControllerFactory factory = classNode.getNodeMetaData(WriterControllerFactory.class); WriterController normalController = new WriterController(); if (factory != null) { this.controller = factory.makeController(normalController); } else { this.controller = normalController; } this.controller.init(this, context, classVisitor, classNode); this.classVisitor = this.controller.getClassVisitor(); if (controller.shouldOptimizeForInt() || factory != null) { OptimizingStatementWriter.setNodeMeta(controller.getTypeChooser(), classNode); } try { int bytecodeVersion = controller.getBytecodeVersion(); Object min = classNode.getNodeMetaData(MINIMUM_BYTECODE_VERSION); if (min instanceof Integer) { int minVersion = (int) min; if ((bytecodeVersion ^ V_PREVIEW) < minVersion) { bytecodeVersion = minVersion; } } classVisitor.visit(bytecodeVersion, adjustedClassModifiersForClassWriting(classNode), controller.getInternalClassName(), BytecodeHelper.getGenericsSignature(classNode), controller.getInternalBaseClassName(), BytecodeHelper.getClassInternalNames(classNode.getInterfaces())); classVisitor.visitSource(sourceFile, null); if (classNode instanceof InnerClassNode) { InnerClassNode innerClass = (InnerClassNode) classNode; MethodNode enclosingMethod = innerClass.getEnclosingMethod(); if (enclosingMethod != null) { String outerClassName = BytecodeHelper .getClassInternalName(innerClass.getOuterClass().getName()); classVisitor.visitOuterClass(outerClassName, enclosingMethod.getName(), BytecodeHelper.getMethodDescriptor(enclosingMethod)); } } if (classNode.getName().endsWith("package-info")) { PackageNode packageNode = classNode.getPackage(); if (packageNode != null) { // pull them out of package node but treat them like they were on class node visitAnnotations(classNode, packageNode, classVisitor); } classVisitor.visitEnd(); return; } else { visitAnnotations(classNode, classVisitor); } if (classNode.isInterface()) { ClassNode owner = classNode; if (owner instanceof InnerClassNode) { owner = owner.getOuterClass(); } String outerClassName = classNode.getName(); String name = outerClassName + "$" + context.getNextInnerClassIdx(); controller.setInterfaceClassLoadingClass( new InterfaceHelperClassNode(owner, name, ACC_SUPER | ACC_SYNTHETIC | ACC_STATIC, ClassHelper.OBJECT_TYPE, controller.getCallSiteWriter().getCallSites())); super.visitClass(classNode); createInterfaceSyntheticStaticFields(); } else { super.visitClass(classNode); MopWriter.Factory mopWriterFactory = classNode.getNodeMetaData(MopWriter.Factory.class); if (mopWriterFactory == null) { mopWriterFactory = MopWriter.FACTORY; } MopWriter mopWriter = mopWriterFactory.create(controller); mopWriter.createMopMethods(); controller.getCallSiteWriter().generateCallSiteArray(); createSyntheticStaticFields(); } // GROOVY-6750 and GROOVY-6808 for (Iterator<InnerClassNode> iter = classNode.getInnerClasses(); iter.hasNext();) { InnerClassNode innerClass = iter.next(); makeInnerClassEntry(innerClass); } makeInnerClassEntry(classNode); classVisitor.visitEnd(); } catch (GroovyRuntimeException e) { e.setModule(classNode.getModule()); throw e; } catch (NegativeArraySizeException nase) { throw new GroovyRuntimeException("NegativeArraySizeException while processing " + sourceFile, nase); } catch (NullPointerException npe) { throw new GroovyRuntimeException("NPE while processing " + sourceFile, npe); } } private void makeInnerClassEntry(final ClassNode cn) { if (!(cn instanceof InnerClassNode)) return; InnerClassNode innerClass = (InnerClassNode) cn; String innerClassName = innerClass.getName(); String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassName); { int index = innerClassName.lastIndexOf('$'); if (index >= 0) innerClassName = innerClassName.substring(index + 1); } String outerClassName = BytecodeHelper.getClassInternalName(innerClass.getOuterClass().getName()); MethodNode enclosingMethod = innerClass.getEnclosingMethod(); if (enclosingMethod != null) { // local inner classes do not specify the outer class name outerClassName = null; if (innerClass.isAnonymous()) innerClassName = null; } int modifiers = adjustedClassModifiersForInnerClassTable(cn); classVisitor.visitInnerClass(innerClassInternalName, outerClassName, innerClassName, modifiers); } /* * See http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.6-300-D.2-5 * for what flags are allowed depending on the fact we are writing the inner class table * or the class itself */ private static int adjustedClassModifiersForInnerClassTable(final ClassNode classNode) { int modifiers = classNode.getModifiers(); modifiers = modifiers & ~ACC_SUPER; modifiers = fixInterfaceModifiers(classNode, modifiers); return modifiers; } private static int fixInterfaceModifiers(final ClassNode classNode, int modifiers) { // (JLS 9.1.1.1). Such a class file must not have its ACC_FINAL, ACC_SUPER or ACC_ENUM flags set. if (classNode.isInterface()) { modifiers = modifiers & ~ACC_ENUM; modifiers = modifiers & ~ACC_FINAL; } return modifiers; } private static int fixInnerClassModifiers(final ClassNode classNode, int modifiers) { // on the inner class node itself, private/protected are not allowed if (classNode.getOuterClass() != null) { if ((modifiers & ACC_PRIVATE) != 0) { // GROOVY-6357: The JVM does not allow private modifier on inner classes: should be package private modifiers = (modifiers & ~ACC_PRIVATE); } if ((modifiers & ACC_PROTECTED) != 0) { // GROOVY-6357: Following Java's behavior for protected modifier on inner classes: should be public modifiers = (modifiers & ~ACC_PROTECTED) | ACC_PUBLIC; } } return modifiers; } /* * Classes but not interfaces should have ACC_SUPER set * See http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.6-300-D.2-5 * for what flags are allowed depending on the fact we are writing the inner class table * or the class itself */ private static int adjustedClassModifiersForClassWriting(final ClassNode classNode) { int modifiers = classNode.getModifiers(); boolean needsSuper = !classNode.isInterface(); modifiers = needsSuper ? modifiers | ACC_SUPER : modifiers & ~ACC_SUPER; // eliminate static modifiers = modifiers & ~ACC_STATIC; modifiers = fixInnerClassModifiers(classNode, modifiers); modifiers = fixInterfaceModifiers(classNode, modifiers); return modifiers; } public void visitGenericType(final GenericsType genericsType) { ClassNode type = genericsType.getType(); genericParameterNames.put(type.getName(), genericsType); } @Override protected void visitConstructorOrMethod(final MethodNode node, final boolean isConstructor) { controller.resetLineNumber(); Parameter[] parameters = node.getParameters(); String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), parameters); String signature = BytecodeHelper.getGenericsMethodSignature(node); int modifiers = node.getModifiers(); if (isVargs(node.getParameters())) modifiers |= ACC_VARARGS; MethodVisitor mv = classVisitor.visitMethod(modifiers, node.getName(), methodType, signature, buildExceptions(node.getExceptions())); controller.setMethodVisitor(mv); visitAnnotations(node, mv); for (int i = 0, n = parameters.length; i < n; i += 1) { visitParameterAnnotations(parameters[i], i, mv); } // add parameter names to the MethodVisitor (jdk8+ only) if (getCompileUnit().getConfig().getParameters()) { for (Parameter parameter : parameters) { // TODO: handle ACC_SYNTHETIC for enum method parameters? mv.visitParameter(parameter.getName(), 0); } } if (controller.getClassNode().isAnnotationDefinition() && !node.isStaticConstructor()) { visitAnnotationDefault(node, mv); } else if (!node.isAbstract()) { Statement code = node.getCode(); mv.visitCode(); // fast path for getter/setters etc. if (code instanceof BytecodeSequence && ((BytecodeSequence) code).getInstructions().size() == 1 && ((BytecodeSequence) code).getInstructions().get(0) instanceof BytecodeInstruction) { ((BytecodeInstruction) ((BytecodeSequence) code).getInstructions().get(0)).visit(mv); } else { visitStdMethod(node, isConstructor, parameters, code); } try { mv.visitMaxs(0, 0); } catch (Exception e) { Writer writer = null; if (mv instanceof TraceMethodVisitor) { TraceMethodVisitor tracer = (TraceMethodVisitor) mv; writer = new StringBuilderWriter(); PrintWriter p = new PrintWriter(writer); tracer.p.print(p); p.flush(); } StringBuilder message = new StringBuilder(64); message.append("ASM reporting processing error for "); message.append(controller.getClassNode().toString()).append("#").append(node.getName()); message.append(" with signature ").append(node.getTypeDescriptor()); message.append(" in ").append(sourceFile).append(":").append(node.getLineNumber()); if (writer != null) { message.append("\nLast known generated bytecode in last generated method or constructor:\n"); message.append(writer); } throw new GroovyRuntimeException(message.toString(), e); } } mv.visitEnd(); } private void visitStdMethod(final MethodNode node, final boolean isConstructor, final Parameter[] parameters, final Statement code) { controller.getCompileStack().init(node.getVariableScope(), parameters); controller.getCallSiteWriter().makeSiteEntry(); MethodVisitor mv = controller.getMethodVisitor(); final ClassNode superClass = controller.getClassNode().getSuperClass(); if (isConstructor && (code == null || !((ConstructorNode) node).firstStatementIsSpecialConstructorCall())) { boolean hasCallToSuper = false; if (code != null && controller.getClassNode() instanceof InnerClassNode) { // if the class not is an inner class node, there are chances that the call to super is already added // so we must ensure not to add it twice (see GROOVY-4471) if (code instanceof BlockStatement) { for (Statement statement : ((BlockStatement) code).getStatements()) { if (statement instanceof ExpressionStatement) { final Expression expression = ((ExpressionStatement) statement).getExpression(); if (expression instanceof ConstructorCallExpression) { ConstructorCallExpression call = (ConstructorCallExpression) expression; if (call.isSuperCall()) { hasCallToSuper = true; break; } } } } } } if (!hasCallToSuper) { // invokes the super class constructor mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(superClass), "<init>", "()V", false); } } // handle body super.visitConstructorOrMethod(node, isConstructor); controller.getCompileStack().clear(); if (node.isVoidMethod()) { mv.visitInsn(RETURN); } else { // we make a dummy return for label ranges that reach here ClassNode type = node.getReturnType().redirect(); if (ClassHelper.isPrimitiveType(type)) { mv.visitLdcInsn(0); controller.getOperandStack().push(ClassHelper.int_TYPE); controller.getOperandStack().doGroovyCast(type); BytecodeHelper.doReturn(mv, type); controller.getOperandStack().remove(1); } else { mv.visitInsn(ACONST_NULL); BytecodeHelper.doReturn(mv, type); } } } private void visitAnnotationDefaultExpression(final AnnotationVisitor av, final ClassNode type, final Expression exp) { if (exp instanceof ClosureExpression) { ClassNode closureClass = controller.getClosureWriter().getOrAddClosureClass((ClosureExpression) exp, ACC_PUBLIC); Type t = Type.getType(BytecodeHelper.getTypeDescription(closureClass)); av.visit(null, t); } else if (type.isArray()) { AnnotationVisitor avl = av.visitArray(null); ClassNode componentType = type.getComponentType(); if (exp instanceof ListExpression) { ListExpression list = (ListExpression) exp; for (Expression lExp : list.getExpressions()) { visitAnnotationDefaultExpression(avl, componentType, lExp); } } else { visitAnnotationDefaultExpression(avl, componentType, exp); } } else if (ClassHelper.isPrimitiveType(type) || type.equals(ClassHelper.STRING_TYPE)) { ConstantExpression constExp = (ConstantExpression) exp; av.visit(null, constExp.getValue()); } else if (ClassHelper.CLASS_Type.equals(type)) { ClassNode clazz = exp.getType(); Type t = Type.getType(BytecodeHelper.getTypeDescription(clazz)); av.visit(null, t); } else if (type.isDerivedFrom(ClassHelper.Enum_Type)) { PropertyExpression pExp = (PropertyExpression) exp; ClassExpression cExp = (ClassExpression) pExp.getObjectExpression(); String desc = BytecodeHelper.getTypeDescription(cExp.getType()); String name = pExp.getPropertyAsString(); av.visitEnum(null, desc, name); } else if (type.implementsInterface(ClassHelper.Annotation_TYPE)) { AnnotationConstantExpression avExp = (AnnotationConstantExpression) exp; AnnotationNode value = (AnnotationNode) avExp.getValue(); AnnotationVisitor avc = av.visitAnnotation(null, BytecodeHelper.getTypeDescription(avExp.getType())); visitAnnotationAttributes(value, avc); } else { throw new GroovyBugError("unexpected annotation type " + type.getName()); } av.visitEnd(); } private void visitAnnotationDefault(final MethodNode node, final MethodVisitor mv) { if (!node.hasAnnotationDefault()) return; Expression exp = ((ReturnStatement) node.getCode()).getExpression(); AnnotationVisitor av = mv.visitAnnotationDefault(); visitAnnotationDefaultExpression(av, node.getReturnType(), exp); } @Override public void visitConstructor(final ConstructorNode node) { controller.setConstructorNode(node); super.visitConstructor(node); } @Override public void visitMethod(final MethodNode node) { controller.setMethodNode(node); super.visitMethod(node); } @Override public void visitField(final FieldNode fieldNode) { onLineNumber(fieldNode, "visitField: " + fieldNode.getName()); ClassNode t = fieldNode.getType(); String signature = BytecodeHelper.getGenericsBounds(t); Expression initialValueExpression = fieldNode.getInitialValueExpression(); ConstantExpression cexp = initialValueExpression instanceof ConstantExpression ? (ConstantExpression) initialValueExpression : null; if (cexp != null) { cexp = Verifier.transformToPrimitiveConstantIfPossible(cexp); } Object value = cexp != null && ClassHelper.isStaticConstantInitializerType(cexp.getType()) && cexp.getType().equals(t) && fieldNode.isStatic() && fieldNode.isFinal() ? cexp.getValue() : null; // GROOVY-5150 if (value != null) { // byte, char and short require an extra cast if (ClassHelper.byte_TYPE.equals(t) || ClassHelper.short_TYPE.equals(t)) { value = ((Number) value).intValue(); } else if (ClassHelper.char_TYPE.equals(t)) { value = Integer.valueOf((Character) value); } } FieldVisitor fv = classVisitor.visitField(fieldNode.getModifiers(), fieldNode.getName(), BytecodeHelper.getTypeDescription(t), signature, value); visitAnnotations(fieldNode, fv); fv.visitEnd(); } @Override public void visitProperty(final PropertyNode statement) { // the verifier created the field and the setter/getter methods, so here is // not really something to do onLineNumber(statement, "visitProperty:" + statement.getField().getName()); controller.setMethodNode(null); } // GroovyCodeVisitor interface //------------------------------------------------------------------------- // Statements //------------------------------------------------------------------------- @Override protected void visitStatement(final Statement statement) { throw new GroovyBugError("visitStatement should not be visited here."); } @Override public void visitCatchStatement(final CatchStatement statement) { statement.getCode().visit(this); } @Override public void visitBlockStatement(final BlockStatement statement) { controller.getStatementWriter().writeBlockStatement(statement); } @Override public void visitForLoop(final ForStatement statement) { controller.getStatementWriter().writeForStatement(statement); } @Override public void visitWhileLoop(final WhileStatement statement) { controller.getStatementWriter().writeWhileLoop(statement); } @Override public void visitDoWhileLoop(final DoWhileStatement statement) { controller.getStatementWriter().writeDoWhileLoop(statement); } @Override public void visitIfElse(final IfStatement statement) { controller.getStatementWriter().writeIfElse(statement); } @Override public void visitAssertStatement(final AssertStatement statement) { controller.getStatementWriter().writeAssert(statement); } @Override public void visitTryCatchFinally(final TryCatchStatement statement) { controller.getStatementWriter().writeTryCatchFinally(statement); } @Override public void visitSwitch(final SwitchStatement statement) { controller.getStatementWriter().writeSwitch(statement); } @Override public void visitCaseStatement(final CaseStatement statement) { } @Override public void visitBreakStatement(final BreakStatement statement) { controller.getStatementWriter().writeBreak(statement); } @Override public void visitContinueStatement(final ContinueStatement statement) { controller.getStatementWriter().writeContinue(statement); } @Override public void visitSynchronizedStatement(final SynchronizedStatement statement) { controller.getStatementWriter().writeSynchronized(statement); } @Override public void visitThrowStatement(final ThrowStatement statement) { controller.getStatementWriter().writeThrow(statement); } @Override public void visitReturnStatement(final ReturnStatement statement) { controller.getStatementWriter().writeReturn(statement); } @Override public void visitExpressionStatement(final ExpressionStatement statement) { controller.getStatementWriter().writeExpressionStatement(statement); } // Expressions //------------------------------------------------------------------------- @Override public void visitTernaryExpression(final TernaryExpression expression) { onLineNumber(expression, "visitTernaryExpression"); controller.getBinaryExpressionHelper().evaluateTernary(expression); } @Override public void visitDeclarationExpression(final DeclarationExpression expression) { onLineNumber(expression, "visitDeclarationExpression: \"" + expression.getText() + "\""); controller.getBinaryExpressionHelper().evaluateEqual(expression, true); } @Override public void visitBinaryExpression(final BinaryExpression expression) { onLineNumber(expression, "visitBinaryExpression: \"" + expression.getOperation().getText() + "\" "); controller.getBinaryExpressionHelper().eval(expression); controller.getAssertionWriter().record(expression.getOperation()); } @Override public void visitPostfixExpression(final PostfixExpression expression) { controller.getBinaryExpressionHelper().evaluatePostfixMethod(expression); controller.getAssertionWriter().record(expression); } @Override public void visitPrefixExpression(final PrefixExpression expression) { controller.getBinaryExpressionHelper().evaluatePrefixMethod(expression); controller.getAssertionWriter().record(expression); } @Override public void visitClosureExpression(final ClosureExpression expression) { controller.getClosureWriter().writeClosure(expression); } @Override public void visitLambdaExpression(final LambdaExpression expression) { controller.getLambdaWriter().writeLambda(expression); } /** * Loads either this object or if we're inside a closure then load the top level owner */ protected void loadThisOrOwner() { if (isInnerClass()) { visitFieldExpression(new FieldExpression(controller.getClassNode().getDeclaredField("owner"))); } else { loadThis(null); } } /** * Generates byte code for constants. * * @see <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152">Class field types</a> */ @Override public void visitConstantExpression(final ConstantExpression expression) { final String constantName = expression.getConstantName(); if (controller.isStaticConstructor() || constantName == null) { controller.getOperandStack().pushConstant(expression); } else { controller.getMethodVisitor().visitFieldInsn(GETSTATIC, controller.getInternalClassName(), constantName, BytecodeHelper.getTypeDescription(expression.getType())); controller.getOperandStack().push(expression.getType()); } } @Override public void visitSpreadExpression(final SpreadExpression expression) { throw new GroovyBugError("SpreadExpression should not be visited here"); } @Override public void visitSpreadMapExpression(final SpreadMapExpression expression) { Expression subExpression = expression.getExpression(); // to not record the underlying MapExpression twice, // we disable the assertion tracker // see https://issues.apache.org/jira/browse/GROOVY-3421 controller.getAssertionWriter().disableTracker(); subExpression.visit(this); controller.getOperandStack().box(); spreadMap.call(controller.getMethodVisitor()); controller.getAssertionWriter().reenableTracker(); controller.getOperandStack().replace(ClassHelper.OBJECT_TYPE); } @Override public void visitMethodPointerExpression(final MethodPointerExpression expression) { controller.getMethodPointerExpressionWriter().writeMethodPointerExpression(expression); } @Override public void visitMethodReferenceExpression(final MethodReferenceExpression expression) { controller.getMethodReferenceExpressionWriter().writeMethodReferenceExpression(expression); } @Override public void visitUnaryMinusExpression(final UnaryMinusExpression expression) { controller.getUnaryExpressionHelper().writeUnaryMinus(expression); } @Override public void visitUnaryPlusExpression(final UnaryPlusExpression expression) { controller.getUnaryExpressionHelper().writeUnaryPlus(expression); } @Override public void visitBitwiseNegationExpression(final BitwiseNegationExpression expression) { controller.getUnaryExpressionHelper().writeBitwiseNegate(expression); } @Override public void visitCastExpression(final CastExpression castExpression) { ClassNode type = castExpression.getType(); Expression subExpression = castExpression.getExpression(); subExpression.visit(this); if (ClassHelper.OBJECT_TYPE.equals(type)) return; if (castExpression.isCoerce()) { controller.getOperandStack().doAsType(type); } else { if (isNullConstant(subExpression) && !ClassHelper.isPrimitiveType(type)) { controller.getOperandStack().replace(type); } else { ClassNode subExprType = controller.getTypeChooser().resolveType(subExpression, controller.getClassNode()); if (castExpression.isStrict() || (!ClassHelper.isPrimitiveType(type) && WideningCategories.implementsInterfaceOrSubclassOf(subExprType, type))) { BytecodeHelper.doCast(controller.getMethodVisitor(), type); controller.getOperandStack().replace(type); } else { controller.getOperandStack().doGroovyCast(type); } } } } @Override public void visitNotExpression(final NotExpression expression) { controller.getUnaryExpressionHelper().writeNotExpression(expression); } @Override public void visitBooleanExpression(final BooleanExpression expression) { controller.getCompileStack().pushBooleanExpression(); int mark = controller.getOperandStack().getStackLength(); Expression inner = expression.getExpression(); inner.visit(this); controller.getOperandStack().castToBool(mark, true); controller.getCompileStack().pop(); } @Override public void visitMethodCallExpression(final MethodCallExpression call) { onLineNumber(call, "visitMethodCallExpression: \"" + call.getMethod() + "\":"); controller.getInvocationWriter().writeInvokeMethod(call); controller.getAssertionWriter().record(call.getMethod()); } @Override public void visitStaticMethodCallExpression(final StaticMethodCallExpression call) { onLineNumber(call, "visitStaticMethodCallExpression: \"" + call.getMethod() + "\":"); controller.getInvocationWriter().writeInvokeStaticMethod(call); controller.getAssertionWriter().record(call); } @Override public void visitConstructorCallExpression(final ConstructorCallExpression call) { onLineNumber(call, "visitConstructorCallExpression: \"" + call.getType().getName() + "\":"); if (call.isSpecialCall()) { controller.getInvocationWriter().writeSpecialConstructorCall(call); return; } controller.getInvocationWriter().writeInvokeConstructor(call); controller.getAssertionWriter().record(call); } private static String makeFieldClassName(final ClassNode type) { String internalName = BytecodeHelper.getClassInternalName(type); StringBuilder ret = new StringBuilder(internalName.length()); for (int i = 0, n = internalName.length(); i < n; i += 1) { char c = internalName.charAt(i); if (c == '/') { ret.append('$'); } else if (c == ';') { //append nothing -> delete ';' } else { ret.append(c); } } return ret.toString(); } private static String getStaticFieldName(final ClassNode type) { ClassNode componentType = type; StringBuilder prefix = new StringBuilder(); for (; componentType.isArray(); componentType = componentType.getComponentType()) { prefix.append("$"); } if (prefix.length() != 0) prefix.insert(0, "array"); String name = prefix + "$class$" + makeFieldClassName(componentType); return name; } private static boolean isValidFieldNodeForByteCodeAccess(final FieldNode fn, final ClassNode accessingNode) { if (fn == null) return false; ClassNode declaringClass = fn.getDeclaringClass(); // same class is always allowed access if (fn.isPublic() || declaringClass.equals(accessingNode)) return true; boolean samePackages = Objects.equals(declaringClass.getPackageName(), accessingNode.getPackageName()); // protected means same class or same package, or subclass if (fn.isProtected() && (samePackages || accessingNode.isDerivedFrom(declaringClass))) { return true; } if (!fn.isPrivate()) { // package private is the only modifier left. It means same package is allowed, subclass not, same class is return samePackages; } return false; } public static FieldNode getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(final ClassNode accessingNode, final ClassNode current, final String name, final boolean skipCurrent) { if (!skipCurrent) { FieldNode currentClassField = current.getDeclaredField(name); if (isValidFieldNodeForByteCodeAccess(currentClassField, accessingNode)) return currentClassField; } for (ClassNode node = current.getSuperClass(); node != null; node = node.getSuperClass()) { FieldNode fn = node.getDeclaredField(name); if (isValidFieldNodeForByteCodeAccess(fn, accessingNode)) return fn; } return null; } private void visitAttributeOrProperty(final PropertyExpression expression, final MethodCallerMultiAdapter adapter) { ClassNode classNode = controller.getClassNode(); String propertyName = expression.getPropertyAsString(); Expression objectExpression = expression.getObjectExpression(); if (objectExpression instanceof ClassExpression && "this".equals(propertyName)) { // we have something like A.B.this, and need to make it // into this.this$0.this$0, where this.this$0 returns // A.B and this.this$0.this$0 return A. ClassNode type = objectExpression.getType(); if (controller.getCompileStack().isInSpecialConstructorCall() && type.equals(classNode.getOuterClass())) { // Outer.this in a special constructor call ConstructorNode ctor = controller.getConstructorNode(); Expression receiver = !classNode.isStaticClass() ? new VariableExpression(ctor.getParameters()[0]) : new ClassExpression(type); receiver.setSourcePosition(expression); receiver.visit(this); return; } MethodVisitor mv = controller.getMethodVisitor(); mv.visitVarInsn(ALOAD, 0); ClassNode iterType = classNode; while (!iterType.equals(type)) { String ownerName = BytecodeHelper.getClassInternalName(iterType); if (iterType.getOuterClass() == null) break; FieldNode thisField = iterType.getField("this$0"); iterType = iterType.getOuterClass(); if (thisField == null) { // closure within inner class while (ClassHelper.isGeneratedFunction(iterType)) { // GROOVY-8881: cater for closures within closures - getThisObject is already outer class of all closures iterType = iterType.getOuterClass(); } mv.visitMethodInsn(INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(ClassHelper.CLOSURE_TYPE), "getThisObject", "()Ljava/lang/Object;", false); mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(iterType)); } else { ClassNode thisFieldType = thisField.getType(); if (ClassHelper.CLOSURE_TYPE.equals(thisFieldType)) { mv.visitFieldInsn(GETFIELD, ownerName, "this$0", BytecodeHelper.getTypeDescription(ClassHelper.CLOSURE_TYPE)); mv.visitMethodInsn(INVOKEVIRTUAL, BytecodeHelper.getClassInternalName(ClassHelper.CLOSURE_TYPE), "getThisObject", "()Ljava/lang/Object;", false); mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(iterType)); } else { String typeName = BytecodeHelper.getTypeDescription(iterType); mv.visitFieldInsn(GETFIELD, ownerName, "this$0", typeName); } } } controller.getOperandStack().push(type); return; } if (propertyName != null) { // TODO: spread safe should be handled inside if (adapter == getProperty && !expression.isSpreadSafe()) { controller.getCallSiteWriter().makeGetPropertySite(objectExpression, propertyName, expression.isSafe(), expression.isImplicitThis()); } else if (adapter == getGroovyObjectProperty && !expression.isSpreadSafe()) { controller.getCallSiteWriter().makeGroovyObjectGetPropertySite(objectExpression, propertyName, expression.isSafe(), expression.isImplicitThis()); } else { controller.getCallSiteWriter().fallbackAttributeOrPropertySite(expression, objectExpression, propertyName, adapter); } } else { controller.getCallSiteWriter().fallbackAttributeOrPropertySite(expression, objectExpression, null, adapter); } } private void setPropertyOfSuperClass(final ClassNode classNode, final PropertyExpression expression, final MethodVisitor mv) { String fieldName = expression.getPropertyAsString(); FieldNode fieldNode = classNode.getSuperClass().getField(fieldName); if (null == fieldNode) { throw new RuntimeParserException( "Failed to find field[" + fieldName + "] of " + classNode.getName() + "'s super class", expression); } if (fieldNode.isFinal()) { throw new RuntimeParserException( "Cannot modify final field[" + fieldName + "] of " + classNode.getName() + "'s super class", expression); } MethodNode setter = findSetterOfSuperClass(classNode, fieldNode); MethodNode getter = findGetterOfSuperClass(classNode, fieldNode); if (fieldNode.isPrivate() && !getterAndSetterExists(setter, getter)) { throw new RuntimeParserException( "Cannot access private field[" + fieldName + "] of " + classNode.getName() + "'s super class", expression); } OperandStack operandStack = controller.getOperandStack(); operandStack.doAsType(fieldNode.getType()); mv.visitVarInsn(ALOAD, 0); operandStack.push(classNode); operandStack.swap(); String owner = BytecodeHelper.getClassInternalName(classNode.getSuperClass().getName()); String desc = BytecodeHelper.getTypeDescription(fieldNode.getType()); if (fieldNode.isPublic() || fieldNode.isProtected()) { mv.visitFieldInsn(PUTFIELD, owner, fieldName, desc); } else { mv.visitMethodInsn(INVOKESPECIAL, owner, setter.getName(), BytecodeHelper.getMethodDescriptor(setter), false); } } private static boolean getterAndSetterExists(final MethodNode setter, final MethodNode getter) { return setter != null && getter != null && setter.getDeclaringClass().equals(getter.getDeclaringClass()); } private static MethodNode findSetterOfSuperClass(final ClassNode classNode, final FieldNode fieldNode) { String setterMethodName = "set" + capitalize(fieldNode.getName()); return classNode.getSuperClass().getSetterMethod(setterMethodName); } private static MethodNode findGetterOfSuperClass(final ClassNode classNode, final FieldNode fieldNode) { String getterMethodName = "get" + capitalize(fieldNode.getName()); return classNode.getSuperClass().getGetterMethod(getterMethodName); } private boolean isGroovyObject(final Expression objectExpression) { if (isThisExpression(objectExpression)) return true; if (objectExpression instanceof ClassExpression) return false; ClassNode objectExpressionType = controller.getTypeChooser().resolveType(objectExpression, controller.getClassNode()); if (objectExpressionType.equals(ClassHelper.OBJECT_TYPE)) objectExpressionType = objectExpression.getType(); return objectExpressionType.isDerivedFromGroovyObject(); } @Override public void visitPropertyExpression(final PropertyExpression expression) { Expression objectExpression = expression.getObjectExpression(); OperandStack operandStack = controller.getOperandStack(); int mark = operandStack.getStackLength() - 1; boolean visited = false; if (isThisOrSuper(objectExpression)) { String name = expression.getPropertyAsString(); if (name != null) { FieldNode field = null; boolean privateSuperField = false; ClassNode classNode = controller.getClassNode(); if (isSuperExpression(objectExpression)) { field = classNode.getSuperClass().getDeclaredField(name); privateSuperField = (field != null && field.isPrivate()); } else if (expression.isImplicitThis() || !controller.isInClosure()) { field = classNode.getDeclaredField(name); ClassNode outer = classNode.getOuterClass(); if (field == null && outer != null) { do { FieldNode outerClassField = outer.getDeclaredField(name); if (outerClassField != null && outerClassField.isStatic() && outerClassField.isFinal()) { if (outerClassField.isPrivate() && classNode.getOuterClass() != outer) { throw new GroovyBugError( "Trying to access private field [" + outerClassField.getDeclaringClass() + "#" + outerClassField.getName() + "] from inner class"); } PropertyExpression staticOuterField = new PropertyExpression( new ClassExpression(outer), expression.getProperty()); staticOuterField.getObjectExpression().setSourcePosition(objectExpression); staticOuterField.visit(controller.getAcg()); return; } outer = outer.getSuperClass(); } while (outer != null); } } if (field != null && !privateSuperField) { // GROOVY-4497: don't visit super field if it is private visitFieldExpression(new FieldExpression(field)); visited = true; } else if (isSuperExpression(objectExpression)) { if (controller.getCompileStack().isLHS()) { setPropertyOfSuperClass(classNode, expression, controller.getMethodVisitor()); } else { visitMethodCallExpression(new MethodCallExpression(objectExpression, "get" + capitalize(name), MethodCallExpression.NO_ARGUMENTS)); } visited = true; } } } if (!visited) { boolean useMetaObjectProtocol = isGroovyObject(objectExpression) && (!isThisOrSuper(objectExpression) || !controller.isStaticContext() || controller.isInClosure()); MethodCallerMultiAdapter adapter; if (controller.getCompileStack().isLHS()) { adapter = useMetaObjectProtocol ? setGroovyObjectProperty : setProperty; } else { adapter = useMetaObjectProtocol ? getGroovyObjectProperty : getProperty; } visitAttributeOrProperty(expression, adapter); } if (controller.getCompileStack().isLHS()) { operandStack.remove(operandStack.getStackLength() - mark); } else { controller.getAssertionWriter().record(expression.getProperty()); } } @Override public void visitAttributeExpression(final AttributeExpression expression) { Expression objectExpression = expression.getObjectExpression(); OperandStack operandStack = controller.getOperandStack(); int mark = operandStack.getStackLength() - 1; boolean visited = false; if (isThisOrSuper(objectExpression)) { String name = expression.getPropertyAsString(); if (name != null) { ClassNode classNode = controller.getClassNode(); FieldNode field = getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(classNode, classNode, name, isSuperExpression(objectExpression)); if (field != null) { visitFieldExpression(new FieldExpression(field)); visited = true; } else if (isSuperExpression(objectExpression)) { if (controller.getCompileStack().isLHS()) { setPropertyOfSuperClass(classNode, expression, controller.getMethodVisitor()); } else { visitMethodCallExpression(new MethodCallExpression(objectExpression, "get" + capitalize(name), MethodCallExpression.NO_ARGUMENTS)); } visited = true; } } } if (!visited) { MethodCallerMultiAdapter adapter; if (controller.getCompileStack().isLHS()) { adapter = isGroovyObject(objectExpression) ? setGroovyObjectField : setField; } else { adapter = isGroovyObject(objectExpression) ? getGroovyObjectField : getField; } visitAttributeOrProperty(expression, adapter); } if (controller.getCompileStack().isLHS()) { operandStack.remove(operandStack.getStackLength() - mark); } else { controller.getAssertionWriter().record(expression.getProperty()); } } @Override public void visitFieldExpression(final FieldExpression expression) { FieldNode field = expression.getField(); if (field.isStatic()) { if (controller.getCompileStack().isLHS()) { storeStaticField(expression); } else { loadStaticField(expression); } } else { if (controller.getCompileStack().isLHS()) { storeThisInstanceField(expression); } else { loadInstanceField(expression); } } } public void loadStaticField(final FieldExpression fldExp) { MethodVisitor mv = controller.getMethodVisitor(); FieldNode field = fldExp.getField(); boolean holder = field.isHolder() && !controller.isInClosureConstructor(); ClassNode type = field.getType(); String ownerName = (field.getOwner().equals(controller.getClassNode())) ? controller.getInternalClassName() : BytecodeHelper.getClassInternalName(field.getOwner()); if (holder) { mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;", false); controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); } else { mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); controller.getOperandStack().push(field.getType()); } } /** * RHS instance field. should move most of the code in the BytecodeHelper */ public void loadInstanceField(final FieldExpression fldExp) { MethodVisitor mv = controller.getMethodVisitor(); FieldNode field = fldExp.getField(); boolean holder = field.isHolder() && !controller.isInClosureConstructor(); ClassNode type = field.getType(); String ownerName = (field.getOwner().equals(controller.getClassNode())) ? controller.getInternalClassName() : BytecodeHelper.getClassInternalName(field.getOwner()); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type)); if (holder) { mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;", false); controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); } else { controller.getOperandStack().push(field.getType()); } } private void storeThisInstanceField(final FieldExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); FieldNode field = expression.getField(); boolean setReferenceFromReference = field.isHolder() && expression.isUseReferenceDirectly(); String ownerName = (field.getOwner().equals(controller.getClassNode())) ? controller.getInternalClassName() : BytecodeHelper.getClassInternalName(field.getOwner()); OperandStack operandStack = controller.getOperandStack(); if (setReferenceFromReference) { // rhs is ready to use reference, just put it in the field mv.visitVarInsn(ALOAD, 0); operandStack.push(controller.getClassNode()); operandStack.swap(); mv.visitFieldInsn(PUTFIELD, ownerName, field.getName(), BytecodeHelper.getTypeDescription(field.getType())); } else if (field.isHolder()) { // rhs is normal value, set the value in the Reference operandStack.doGroovyCast(field.getOriginType()); operandStack.box(); mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(field.getType())); mv.visitInsn(SWAP); mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V", false); } else { // rhs is normal value, set normal value operandStack.doGroovyCast(field.getOriginType()); mv.visitVarInsn(ALOAD, 0); operandStack.push(controller.getClassNode()); operandStack.swap(); mv.visitFieldInsn(PUTFIELD, ownerName, field.getName(), BytecodeHelper.getTypeDescription(field.getType())); } } private void storeStaticField(final FieldExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); FieldNode field = expression.getField(); boolean holder = field.isHolder() && !controller.isInClosureConstructor(); controller.getOperandStack().doGroovyCast(field); String ownerName = (field.getOwner().equals(controller.getClassNode())) ? controller.getInternalClassName() : BytecodeHelper.getClassInternalName(field.getOwner()); if (holder) { controller.getOperandStack().box(); mv.visitFieldInsn(GETSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(field.getType())); mv.visitInsn(SWAP); mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V", false); } else { mv.visitFieldInsn(PUTSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(field.getType())); } controller.getOperandStack().remove(1); } @Override public void visitVariableExpression(final VariableExpression expression) { String variableName = expression.getName(); //----------------------------------------------------------------------- // SPECIAL CASES // "this" for static methods is the Class instance ClassNode classNode = controller.getClassNode(); if (expression.isThisExpression()) { if (controller.isStaticMethod() || (!controller.getCompileStack().isImplicitThis() && controller.isStaticContext())) { if (controller.isInClosure()) classNode = controller.getOutermostClass(); visitClassExpression(new ClassExpression(classNode)); } else { loadThis(expression); } return; } // "super" also requires special handling if (expression.isSuperExpression()) { if (controller.isStaticMethod()) { visitClassExpression(new ClassExpression(classNode.getSuperClass())); } else { loadThis(expression); } return; } BytecodeVariable variable = controller.getCompileStack().getVariable(variableName, false); if (variable == null) { processClassVariable(expression); } else { controller.getOperandStack().loadOrStoreVariable(variable, expression.isUseReferenceDirectly()); } if (!controller.getCompileStack().isLHS()) { controller.getAssertionWriter().record(expression); } } private void loadThis(final VariableExpression thisExpression) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitVarInsn(ALOAD, 0); if (controller.isInClosure() && !controller.getCompileStack().isImplicitThis()) { mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Closure", "getThisObject", "()Ljava/lang/Object;", false); ClassNode expectedType = thisExpression != null ? controller.getTypeChooser().resolveType(thisExpression, controller.getOutermostClass()) : null; if (!ClassHelper.OBJECT_TYPE.equals(expectedType) && !ClassHelper.isPrimitiveType(expectedType)) { BytecodeHelper.doCast(mv, expectedType); controller.getOperandStack().push(expectedType); } else { controller.getOperandStack().push(ClassHelper.OBJECT_TYPE); } } else { controller.getOperandStack().push(controller.getClassNode()); } } private void processClassVariable(final VariableExpression expression) { if (passingParams && controller.isInScriptBody()) { //TODO: check if this part is actually used MethodVisitor mv = controller.getMethodVisitor(); // let's create a ScriptReference to pass into the closure mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/ScriptReference"); mv.visitInsn(DUP); loadThisOrOwner(); mv.visitLdcInsn(expression.getName()); mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/ScriptReference", "<init>", "(Lgroovy/lang/Script;Ljava/lang/String;)V", false); } else { PropertyExpression pexp = new PropertyExpression(new VariableExpression("this"), expression.getName()); pexp.getObjectExpression().setSourcePosition(expression); pexp.getProperty().setSourcePosition(expression); pexp.setImplicitThis(true); visitPropertyExpression(pexp); } } protected void createInterfaceSyntheticStaticFields() { ClassNode icl = controller.getInterfaceClassLoadingClass(); if (referencedClasses.isEmpty()) { Iterator<InnerClassNode> it = icl.getOuterClass().getInnerClasses(); while (it.hasNext()) { InnerClassNode inner = it.next(); if (inner == icl) { it.remove(); return; } } return; } addInnerClass(icl); for (Map.Entry<String, ClassNode> entry : referencedClasses.entrySet()) { // generate a field node String staticFieldName = entry.getKey(); ClassNode cn = entry.getValue(); icl.addField(staticFieldName, ACC_STATIC + ACC_SYNTHETIC, ClassHelper.CLASS_Type.getPlainNodeReference(), new ClassExpression(cn)); } } protected void createSyntheticStaticFields() { if (referencedClasses.isEmpty()) { return; } MethodVisitor mv; for (Map.Entry<String, ClassNode> entry : referencedClasses.entrySet()) { String staticFieldName = entry.getKey(); ClassNode cn = entry.getValue(); // generate a field node FieldNode fn = controller.getClassNode().getDeclaredField(staticFieldName); if (fn != null) { boolean type = fn.getType().equals(ClassHelper.CLASS_Type); boolean modifiers = fn.getModifiers() == ACC_STATIC + ACC_SYNTHETIC; if (!type || !modifiers) { String text = ""; if (!type) text = " with wrong type: " + fn.getType() + " (java.lang.Class needed)"; if (!modifiers) text = " with wrong modifiers: " + fn.getModifiers() + " (" + (ACC_STATIC + ACC_SYNTHETIC) + " needed)"; throwException("tried to set a static synthetic field " + staticFieldName + " in " + controller.getClassNode().getName() + " for class resolving, but found already a node of that name " + text); } } else { classVisitor.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, staticFieldName, "Ljava/lang/Class;", null, null); } mv = classVisitor.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$get$" + staticFieldName, "()Ljava/lang/Class;", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, controller.getInternalClassName(), staticFieldName, "Ljava/lang/Class;"); mv.visitInsn(DUP); Label l0 = new Label(); mv.visitJumpInsn(IFNONNULL, l0); mv.visitInsn(POP); mv.visitLdcInsn(BytecodeHelper.getClassLoadingTypeDescription(cn)); mv.visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), "class$", "(Ljava/lang/String;)Ljava/lang/Class;", false); mv.visitInsn(DUP); mv.visitFieldInsn(PUTSTATIC, controller.getInternalClassName(), staticFieldName, "Ljava/lang/Class;"); mv.visitLabel(l0); mv.visitInsn(ARETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } mv = classVisitor.visitMethod(ACC_STATIC + ACC_SYNTHETIC, "class$", "(Ljava/lang/String;)Ljava/lang/Class;", null, null); Label l0 = new Label(); mv.visitLabel(l0); mv.visitVarInsn(ALOAD, 0); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;", false); Label l1 = new Label(); mv.visitLabel(l1); mv.visitInsn(ARETURN); Label l2 = new Label(); mv.visitLabel(l2); mv.visitVarInsn(ASTORE, 1); mv.visitTypeInsn(NEW, "java/lang/NoClassDefFoundError"); mv.visitInsn(DUP); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassNotFoundException", "getMessage", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); mv.visitTryCatchBlock(l0, l2, l2, "java/lang/ClassNotFoundException"); // br using l2 as the 2nd param seems create the right table entry mv.visitMaxs(3, 2); } @Override public void visitClassExpression(final ClassExpression expression) { ClassNode type = expression.getType(); MethodVisitor mv = controller.getMethodVisitor(); if (BytecodeHelper.isClassLiteralPossible(type) || BytecodeHelper.isSameCompilationUnit(controller.getClassNode(), type)) { if (controller.getClassNode().isInterface()) { InterfaceHelperClassNode interfaceClassLoadingClass = controller.getInterfaceClassLoadingClass(); if (BytecodeHelper.isClassLiteralPossible(interfaceClassLoadingClass)) { BytecodeHelper.visitClassLiteral(mv, interfaceClassLoadingClass); controller.getOperandStack().push(ClassHelper.CLASS_Type); return; } } else { BytecodeHelper.visitClassLiteral(mv, type); controller.getOperandStack().push(ClassHelper.CLASS_Type); return; } } String staticFieldName = getStaticFieldName(type); referencedClasses.put(staticFieldName, type); String internalClassName = controller.getInternalClassName(); if (controller.getClassNode().isInterface()) { internalClassName = BytecodeHelper.getClassInternalName(controller.getInterfaceClassLoadingClass()); mv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;"); } else { mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$get$" + staticFieldName, "()Ljava/lang/Class;", false); } controller.getOperandStack().push(ClassHelper.CLASS_Type); } @Override public void visitRangeExpression(final RangeExpression expression) { OperandStack operandStack = controller.getOperandStack(); expression.getFrom().visit(this); operandStack.box(); expression.getTo().visit(this); operandStack.box(); operandStack.pushBool(expression.isInclusive()); createRangeMethod.call(controller.getMethodVisitor()); operandStack.replace(ClassHelper.RANGE_TYPE, 3); } @Override public void visitMapEntryExpression(final MapEntryExpression expression) { throw new GroovyBugError("MapEntryExpression should not be visited here"); } @Override public void visitMapExpression(final MapExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); List<MapEntryExpression> entries = expression.getMapEntryExpressions(); int size = entries.size(); BytecodeHelper.pushConstant(mv, size * 2); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); int i = 0; for (Object object : entries) { MapEntryExpression entry = (MapEntryExpression) object; mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i++); entry.getKeyExpression().visit(this); controller.getOperandStack().box(); mv.visitInsn(AASTORE); mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i++); entry.getValueExpression().visit(this); controller.getOperandStack().box(); mv.visitInsn(AASTORE); controller.getOperandStack().remove(2); } createMapMethod.call(mv); controller.getOperandStack().push(ClassHelper.MAP_TYPE); } @Override public void visitArgumentlistExpression(final ArgumentListExpression ale) { if (containsSpreadExpression(ale)) { despreadList(ale.getExpressions(), true); } else { visitTupleExpression(ale, true); } } public void despreadList(final List<Expression> expressions, final boolean wrap) { List<Expression> spreadIndexes = new ArrayList<>(); List<Expression> spreadExpressions = new ArrayList<>(); List<Expression> normalArguments = new ArrayList<>(); for (int i = 0, n = expressions.size(); i < n; i += 1) { Expression expr = expressions.get(i); if (!(expr instanceof SpreadExpression)) { normalArguments.add(expr); } else { spreadIndexes.add(new ConstantExpression(i - spreadExpressions.size(), true)); spreadExpressions.add(((SpreadExpression) expr).getExpression()); } } // load normal arguments as array visitTupleExpression(new ArgumentListExpression(normalArguments), wrap); // load spread expressions as array new TupleExpression(spreadExpressions).visit(this); // load insertion index new ArrayExpression(ClassHelper.int_TYPE, spreadIndexes, null).visit(this); controller.getOperandStack().remove(1); despreadList.call(controller.getMethodVisitor()); } @Override public void visitTupleExpression(final TupleExpression expression) { visitTupleExpression(expression, false); } void visitTupleExpression(final TupleExpression expression, final boolean useWrapper) { MethodVisitor mv = controller.getMethodVisitor(); int size = expression.getExpressions().size(); BytecodeHelper.pushConstant(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); for (int i = 0; i < size; i += 1) { mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i); Expression argument = expression.getExpression(i); argument.visit(this); controller.getOperandStack().box(); if (useWrapper && argument instanceof CastExpression) loadWrapper(argument); mv.visitInsn(AASTORE); controller.getOperandStack().remove(1); } } public void loadWrapper(final Expression argument) { MethodVisitor mv = controller.getMethodVisitor(); ClassNode goalClass = argument.getType(); visitClassExpression(new ClassExpression(goalClass)); if (goalClass.isDerivedFromGroovyObject()) { createGroovyObjectWrapperMethod.call(mv); } else { createPojoWrapperMethod.call(mv); } controller.getOperandStack().remove(1); } @Override public void visitArrayExpression(final ArrayExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); ClassNode elementType = expression.getElementType(); String arrayTypeName = BytecodeHelper.getClassInternalName(elementType); List<Expression> sizeExpression = expression.getSizeExpression(); int size = 0; int dimensions = 0; if (sizeExpression != null) { for (Expression element : sizeExpression) { if (element == ConstantExpression.EMPTY_EXPRESSION) break; dimensions += 1; // let's convert to an int element.visit(this); controller.getOperandStack().doGroovyCast(ClassHelper.int_TYPE); } controller.getOperandStack().remove(dimensions); } else { size = expression.getExpressions().size(); BytecodeHelper.pushConstant(mv, size); } int storeIns = AASTORE; if (sizeExpression != null) { arrayTypeName = BytecodeHelper.getTypeDescription(expression.getType()); mv.visitMultiANewArrayInsn(arrayTypeName, dimensions); } else if (ClassHelper.isPrimitiveType(elementType)) { int primType = 0; if (elementType == ClassHelper.boolean_TYPE) { primType = T_BOOLEAN; storeIns = BASTORE; } else if (elementType == ClassHelper.char_TYPE) { primType = T_CHAR; storeIns = CASTORE; } else if (elementType == ClassHelper.float_TYPE) { primType = T_FLOAT; storeIns = FASTORE; } else if (elementType == ClassHelper.double_TYPE) { primType = T_DOUBLE; storeIns = DASTORE; } else if (elementType == ClassHelper.byte_TYPE) { primType = T_BYTE; storeIns = BASTORE; } else if (elementType == ClassHelper.short_TYPE) { primType = T_SHORT; storeIns = SASTORE; } else if (elementType == ClassHelper.int_TYPE) { primType = T_INT; storeIns = IASTORE; } else if (elementType == ClassHelper.long_TYPE) { primType = T_LONG; storeIns = LASTORE; } mv.visitIntInsn(NEWARRAY, primType); } else { mv.visitTypeInsn(ANEWARRAY, arrayTypeName); } for (int i = 0; i < size; i += 1) { mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i); Expression elementExpression = expression.getExpression(i); if (elementExpression == null) { ConstantExpression.NULL.visit(this); } else { elementExpression.visit(this); controller.getOperandStack().doGroovyCast(elementType); } mv.visitInsn(storeIns); controller.getOperandStack().remove(1); } controller.getOperandStack().push(expression.getType()); } @Override public void visitClosureListExpression(final ClosureListExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); controller.getCompileStack().pushVariableScope(expression.getVariableScope()); List<Expression> expressions = expression.getExpressions(); final int size = expressions.size(); // init declarations for (int i = 0; i < size; i += 1) { Expression expr = expressions.get(i); if (expr instanceof DeclarationExpression) { DeclarationExpression de = (DeclarationExpression) expr; BinaryExpression be = new BinaryExpression(de.getLeftExpression(), de.getOperation(), de.getRightExpression()); expressions.set(i, be); de.setRightExpression(ConstantExpression.NULL); visitDeclarationExpression(de); } } List<Object> instructions = new LinkedList<>(); BytecodeSequence seq = new BytecodeSequence(instructions); BlockStatement bs = new BlockStatement(); bs.addStatement(seq); Parameter closureIndex = new Parameter(ClassHelper.int_TYPE, "__closureIndex"); ClosureExpression ce = new ClosureExpression(new Parameter[] { closureIndex }, bs); ce.setVariableScope(expression.getVariableScope()); // to keep stack height put a null on stack instructions.add(ConstantExpression.NULL); // init table final Label dflt = new Label(); final Label tableEnd = new Label(); final Label[] labels = new Label[size]; instructions.add(new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitVarInsn(ILOAD, 1); mv.visitTableSwitchInsn(0, size - 1, dflt, labels); } }); // visit cases for (int i = 0; i < size; i += 1) { Label label = new Label(); Expression expr = expressions.get(i); labels[i] = label; instructions.add(new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitLabel(label); // expressions will leave a value on stack, so need to pop the alibi null mv.visitInsn(POP); } }); instructions.add(expr); instructions.add(new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitJumpInsn(GOTO, tableEnd); } }); } // default case instructions.add(new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitLabel(dflt); } }); ConstantExpression text = new ConstantExpression("invalid index for closure"); ConstructorCallExpression cce = new ConstructorCallExpression( ClassHelper.make(IllegalArgumentException.class), text); ThrowStatement ts = new ThrowStatement(cce); instructions.add(ts); // return instructions.add(new BytecodeInstruction() { public void visit(MethodVisitor mv) { mv.visitLabel(tableEnd); mv.visitInsn(ARETURN); } }); // load main Closure visitClosureExpression(ce); // we need later an array to store the curried // closures, so we create it here and ave it // in a temporary variable BytecodeHelper.pushConstant(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); int listArrayVar = controller.getCompileStack().defineTemporaryVariable("_listOfClosures", true); // add curried versions for (int i = 0; i < size; i += 1) { // stack: closure // we need to create a curried closure version // so we store the type on stack mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/CurriedClosure"); // stack: closure, type // for a constructor call we need the type two times // and the closure after them mv.visitInsn(DUP2); mv.visitInsn(SWAP); // stack: closure,type,type,closure // so we can create the curried closure mv.visitInsn(ICONST_1); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); mv.visitInsn(DUP); mv.visitInsn(ICONST_0); mv.visitLdcInsn(i); mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); mv.visitInsn(AASTORE); mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/CurriedClosure", "<init>", "(Lgroovy/lang/Closure;[Ljava/lang/Object;)V", false); // stack: closure,curriedClosure // we need to save the result mv.visitVarInsn(ALOAD, listArrayVar); mv.visitInsn(SWAP); BytecodeHelper.pushConstant(mv, i); mv.visitInsn(SWAP); mv.visitInsn(AASTORE); // stack: closure } // we don't need the closure any longer, so remove it mv.visitInsn(POP); // we load the array and create a list from it mv.visitVarInsn(ALOAD, listArrayVar); createListMethod.call(mv); // remove the temporary variable to keep the // stack clean controller.getCompileStack().removeVar(listArrayVar); controller.getOperandStack().pop(); } @Override public void visitBytecodeExpression(final BytecodeExpression expression) { expression.visit(controller.getMethodVisitor()); controller.getOperandStack().push(expression.getType()); } @Override public void visitBytecodeSequence(final BytecodeSequence bytecodeSequence) { MethodVisitor mv = controller.getMethodVisitor(); List instructions = bytecodeSequence.getInstructions(); int mark = controller.getOperandStack().getStackLength(); for (Object part : instructions) { if (part instanceof EmptyExpression) { mv.visitInsn(ACONST_NULL); } else if (part instanceof Expression) { ((Expression) part).visit(this); } else if (part instanceof Statement) { Statement stm = (Statement) part; stm.visit(this); mv.visitInsn(ACONST_NULL); } else { BytecodeInstruction runner = (BytecodeInstruction) part; runner.visit(mv); } } controller.getOperandStack().remove(mark - controller.getOperandStack().getStackLength()); } @Override public void visitListExpression(final ListExpression expression) { onLineNumber(expression, "ListExpression"); int size = expression.getExpressions().size(); boolean containsSpreadExpression = containsSpreadExpression(expression); boolean containsOnlyConstants = !containsSpreadExpression && containsOnlyConstants(expression); OperandStack operandStack = controller.getOperandStack(); if (!containsSpreadExpression) { MethodVisitor mv = controller.getMethodVisitor(); BytecodeHelper.pushConstant(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); int maxInit = 1000; if (size < maxInit || !containsOnlyConstants) { for (int i = 0; i < size; i += 1) { mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i); expression.getExpression(i).visit(this); operandStack.box(); mv.visitInsn(AASTORE); } controller.getOperandStack().remove(size); } else { List<Expression> expressions = expression.getExpressions(); List<String> methods = new ArrayList<>(); MethodVisitor oldMv = mv; int index = 0; while (index < size) { String methodName = "$createListEntry_" + controller.getNextHelperMethodIndex(); methods.add(methodName); mv = controller.getClassVisitor().visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, methodName, "([Ljava/lang/Object;)V", null, null); controller.setMethodVisitor(mv); mv.visitCode(); int methodBlockSize = Math.min(size - index, maxInit); int methodBlockEnd = index + methodBlockSize; for (; index < methodBlockEnd; index += 1) { mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn(index); expressions.get(index).visit(this); operandStack.box(); mv.visitInsn(AASTORE); } operandStack.remove(methodBlockSize); mv.visitInsn(RETURN); mv.visitMaxs(0, 0); mv.visitEnd(); } mv = oldMv; controller.setMethodVisitor(mv); for (String methodName : methods) { mv.visitInsn(DUP); mv.visitMethodInsn(INVOKESTATIC, controller.getInternalClassName(), methodName, "([Ljava/lang/Object;)V", false); } } } else { despreadList(expression.getExpressions(), false); } createListMethod.call(controller.getMethodVisitor()); operandStack.push(ClassHelper.LIST_TYPE); } @Override public void visitGStringExpression(final GStringExpression expression) { MethodVisitor mv = controller.getMethodVisitor(); mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/GStringImpl"); mv.visitInsn(DUP); int size = expression.getValues().size(); BytecodeHelper.pushConstant(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); for (int i = 0; i < size; i += 1) { mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i); expression.getValue(i).visit(this); controller.getOperandStack().box(); mv.visitInsn(AASTORE); } controller.getOperandStack().remove(size); List<ConstantExpression> strings = expression.getStrings(); size = strings.size(); BytecodeHelper.pushConstant(mv, size); mv.visitTypeInsn(ANEWARRAY, "java/lang/String"); for (int i = 0; i < size; i += 1) { mv.visitInsn(DUP); BytecodeHelper.pushConstant(mv, i); controller.getOperandStack().pushConstant(strings.get(i)); controller.getOperandStack().box(); mv.visitInsn(AASTORE); } controller.getOperandStack().remove(size); mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/GStringImpl", "<init>", "([Ljava/lang/Object;[Ljava/lang/String;)V", false); controller.getOperandStack().push(ClassHelper.GSTRING_TYPE); } @Override public void visitAnnotations(final AnnotatedNode node) { // ignore it; annotation generation needs the current visitor } private void visitAnnotations(final AnnotatedNode targetNode, final Object visitor) { visitAnnotations(targetNode, targetNode, visitor); } private void visitAnnotations(final AnnotatedNode targetNode, final AnnotatedNode sourceNode, final Object visitor) { for (AnnotationNode an : sourceNode.getAnnotations()) { // skip built-in properties if (an.isBuiltIn()) continue; if (an.hasSourceRetention()) continue; AnnotationVisitor av = getAnnotationVisitor(targetNode, an, visitor); visitAnnotationAttributes(an, av); av.visitEnd(); } } private void visitParameterAnnotations(final Parameter parameter, final int paramNumber, final MethodVisitor mv) { for (AnnotationNode an : parameter.getAnnotations()) { // skip built-in properties if (an.isBuiltIn()) continue; if (an.hasSourceRetention()) continue; final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode()); AnnotationVisitor av = mv.visitParameterAnnotation(paramNumber, annotationDescriptor, an.hasRuntimeRetention()); visitAnnotationAttributes(an, av); av.visitEnd(); } } private AnnotationVisitor getAnnotationVisitor(final AnnotatedNode targetNode, final AnnotationNode an, final Object visitor) { final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode()); if (targetNode instanceof MethodNode) { return ((MethodVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); } else if (targetNode instanceof FieldNode) { return ((FieldVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); } else if (targetNode instanceof ClassNode) { return ((ClassVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention()); } throwException("Cannot create an AnnotationVisitor. Please report Groovy bug"); return null; } /** * Generates the annotation attributes. * * @param an the node with an annotation * @param av the visitor to use */ private void visitAnnotationAttributes(final AnnotationNode an, final AnnotationVisitor av) { Map<String, Object> constantAttrs = new HashMap<>(); Map<String, PropertyExpression> enumAttrs = new HashMap<>(); Map<String, Object> atAttrs = new HashMap<>(); Map<String, ListExpression> arrayAttrs = new HashMap<>(); for (Map.Entry<String, Expression> member : an.getMembers().entrySet()) { String name = member.getKey(); Expression expr = member.getValue(); if (expr instanceof AnnotationConstantExpression) { atAttrs.put(name, ((AnnotationConstantExpression) expr).getValue()); } else if (expr instanceof ConstantExpression) { constantAttrs.put(name, ((ConstantExpression) expr).getValue()); } else if (expr instanceof ClassExpression) { constantAttrs.put(name, Type.getType(BytecodeHelper.getTypeDescription((expr.getType())))); } else if (expr instanceof PropertyExpression) { enumAttrs.put(name, (PropertyExpression) expr); } else if (expr instanceof ListExpression) { arrayAttrs.put(name, (ListExpression) expr); } else if (expr instanceof ClosureExpression) { ClassNode closureClass = controller.getClosureWriter() .getOrAddClosureClass((ClosureExpression) expr, ACC_PUBLIC); constantAttrs.put(name, Type.getType(BytecodeHelper.getTypeDescription(closureClass))); } } for (Map.Entry<String, Object> entry : constantAttrs.entrySet()) { av.visit(entry.getKey(), entry.getValue()); } for (Map.Entry<String, PropertyExpression> entry : enumAttrs.entrySet()) { PropertyExpression propExp = entry.getValue(); av.visitEnum(entry.getKey(), BytecodeHelper.getTypeDescription(propExp.getObjectExpression().getType()), String.valueOf(((ConstantExpression) propExp.getProperty()).getValue())); } for (Map.Entry<String, Object> entry : atAttrs.entrySet()) { AnnotationNode atNode = (AnnotationNode) entry.getValue(); AnnotationVisitor av2 = av.visitAnnotation(entry.getKey(), BytecodeHelper.getTypeDescription(atNode.getClassNode())); visitAnnotationAttributes(atNode, av2); av2.visitEnd(); } visitArrayAttributes(an, arrayAttrs, av); } private void visitArrayAttributes(final AnnotationNode an, final Map<String, ListExpression> arrayAttr, final AnnotationVisitor av) { if (arrayAttr.isEmpty()) return; for (Map.Entry<String, ListExpression> entry : arrayAttr.entrySet()) { AnnotationVisitor av2 = av.visitArray(entry.getKey()); List<Expression> values = entry.getValue().getExpressions(); if (!values.isEmpty()) { int arrayElementType = determineCommonArrayType(values); for (Expression exprChild : values) { visitAnnotationArrayElement(exprChild, arrayElementType, av2); } } av2.visitEnd(); } } private static int determineCommonArrayType(final List<Expression> values) { Expression expr = values.get(0); int arrayElementType = -1; if (expr instanceof AnnotationConstantExpression) { arrayElementType = 1; } else if (expr instanceof ConstantExpression) { arrayElementType = 2; } else if (expr instanceof ClassExpression) { arrayElementType = 3; } else if (expr instanceof PropertyExpression) { arrayElementType = 4; } return arrayElementType; } private void visitAnnotationArrayElement(final Expression expr, final int arrayElementType, final AnnotationVisitor av) { switch (arrayElementType) { case 1: AnnotationNode atAttr = (AnnotationNode) ((AnnotationConstantExpression) expr).getValue(); AnnotationVisitor av2 = av.visitAnnotation(null, BytecodeHelper.getTypeDescription(atAttr.getClassNode())); visitAnnotationAttributes(atAttr, av2); av2.visitEnd(); break; case 2: av.visit(null, ((ConstantExpression) expr).getValue()); break; case 3: av.visit(null, Type.getType(BytecodeHelper.getTypeDescription(expr.getType()))); break; case 4: PropertyExpression propExpr = (PropertyExpression) expr; av.visitEnum(null, BytecodeHelper.getTypeDescription(propExpr.getObjectExpression().getType()), String.valueOf(((ConstantExpression) propExpr.getProperty()).getValue())); break; } } // Implementation methods //------------------------------------------------------------------------- public static int argumentSize(final Expression arguments) { if (arguments instanceof TupleExpression) { TupleExpression tupleExpression = (TupleExpression) arguments; int size = tupleExpression.getExpressions().size(); return size; } return 1; } private static String[] buildExceptions(final ClassNode[] exceptions) { if (exceptions == null) return null; return Arrays.stream(exceptions).map(BytecodeHelper::getClassInternalName).toArray(String[]::new); } private static boolean containsOnlyConstants(final ListExpression list) { for (Expression exp : list.getExpressions()) { if (exp instanceof ConstantExpression) continue; return false; } return true; } public static boolean containsSpreadExpression(final Expression arguments) { List<Expression> args = null; if (arguments instanceof TupleExpression) { TupleExpression tupleExpression = (TupleExpression) arguments; args = tupleExpression.getExpressions(); } else if (arguments instanceof ListExpression) { ListExpression le = (ListExpression) arguments; args = le.getExpressions(); } else { return arguments instanceof SpreadExpression; } for (Expression arg : args) { if (arg instanceof SpreadExpression) return true; } return false; } private boolean isInnerClass() { return controller.getClassNode().getOuterClass() != null; } public static boolean isNullConstant(final Expression expression) { return expression instanceof ConstantExpression && ((ConstantExpression) expression).isNullExpression(); } public static boolean isThisExpression(final Expression expression) { return expression instanceof VariableExpression && ((VariableExpression) expression).isThisExpression(); } public static boolean isSuperExpression(final Expression expression) { return expression instanceof VariableExpression && ((VariableExpression) expression).isSuperExpression(); } private static boolean isThisOrSuper(final Expression expression) { return isThisExpression(expression) || isSuperExpression(expression); } private static boolean isVargs(final Parameter[] params) { return (params.length > 0 && params[params.length - 1].getType().isArray()); } private CompileUnit getCompileUnit() { CompileUnit answer = controller.getClassNode().getCompileUnit(); if (answer == null) { answer = context.getCompileUnit(); } return answer; } public boolean addInnerClass(final ClassNode innerClass) { ModuleNode mn = controller.getClassNode().getModule(); innerClass.setModule(mn); mn.getUnit().addGeneratedInnerClass((InnerClassNode) innerClass); return innerClasses.add(innerClass); } public void onLineNumber(final ASTNode statement, final String message) { if (statement == null || statement instanceof BlockStatement) return; currentASTNode = statement; int line = statement.getLineNumber(); if (line < 0 || (!ASM_DEBUG && line == controller.getLineNumber())) return; controller.setLineNumber(line); MethodVisitor mv = controller.getMethodVisitor(); if (mv != null) { Label l = new Label(); mv.visitLabel(l); mv.visitLineNumber(line, l); } } public void throwException(final String message) { throw new RuntimeParserException(message, currentASTNode); } }