diff --git a/AUTHORS b/AUTHORS index 91d6bd7bc4..df118eaf09 100755 --- a/AUTHORS +++ b/AUTHORS @@ -54,5 +54,6 @@ Till Brychcy Victor Williams Stafusa da Silva Yonatan Sherwin Yun Zhi Lin +Yoon Tae Min By adding your name to this list, you grant full and irrevocable copyright and patent indemnity to Project Lombok and all use of Project Lombok in relation to all commits you add to Project Lombok, and you certify that you have the right to do so. diff --git a/src/core/lombok/ConfigurationKeys.java b/src/core/lombok/ConfigurationKeys.java index 22d5a4c5a8..6e6fef7b0d 100644 --- a/src/core/lombok/ConfigurationKeys.java +++ b/src/core/lombok/ConfigurationKeys.java @@ -651,6 +651,14 @@ private ConfigurationKeys() {} */ public static final ConfigurationKey FIELD_NAME_CONSTANTS_FLAG_USAGE = new ConfigurationKey("lombok.fieldNameConstants.flagUsage", "Emit a warning or error if @FieldNameConstants is used.") {}; + // ----- Generator ----- + /** + * lombok configuration: {@code lombok.generator.flagUsage} = {@code WARNING} | {@code ERROR}. + * + * If set, any usage of {@code @Generator} results in a warning / error. + */ + public static final ConfigurationKey GENERATOR_FLAG_USAGE = new ConfigurationKey("lombok.generator.flagUsage", "Emit a warning or error if @Generator is used.") {}; + /** * lombok configuration: {@code lombok.fieldNameConstants.innerTypeName} = <String: AValidJavaTypeName> (Default: {@code Fields}). * diff --git a/src/core/lombok/Lombok.java b/src/core/lombok/Lombok.java index d86b6d1cce..93fadc201f 100644 --- a/src/core/lombok/Lombok.java +++ b/src/core/lombok/Lombok.java @@ -21,6 +21,8 @@ */ package lombok; +import java.util.Iterator; + /** * Useful utility methods to manipulate lombok-generated code. */ @@ -82,4 +84,39 @@ public static T checkNotNull(T value, String message) { if (value == null) throw new NullPointerException(message); return value; } + + /** + * Stub class for {@code GeneratorPostTransformation} + */ + public static abstract class Generator implements Iterable, Iterator { + + protected abstract void advance(); + + protected final void yieldThis(T target) { + throw new UnsupportedOperationException("Unimplemented method 'yieldThis'"); + } + + protected final void yieldAll(Iterable target) { + throw new UnsupportedOperationException("Unimplemented method 'yieldAll'"); + } + protected final void yieldAll(T[] target) { + throw new UnsupportedOperationException("Unimplemented method 'yieldAll'"); + } + + @Override public boolean hasNext() { + throw new UnsupportedOperationException("Unimplemented method 'hasNext'"); + } + + @Override public T next() { + throw new UnsupportedOperationException("Unimplemented method 'next'"); + } + + @Override public Iterator iterator() { + throw new UnsupportedOperationException("Unimplemented method 'iterator'"); + } + + @Override public void remove() { + throw new UnsupportedOperationException("Unimplemented method 'remove'"); + } + } } diff --git a/src/core/lombok/bytecode/GeneratorPostTransformation.java b/src/core/lombok/bytecode/GeneratorPostTransformation.java new file mode 100644 index 0000000000..1d6829ccf8 --- /dev/null +++ b/src/core/lombok/bytecode/GeneratorPostTransformation.java @@ -0,0 +1,916 @@ +/* + * Copyright (C) 2023 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.bytecode; + +import static lombok.bytecode.AsmUtil.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import lombok.core.DiagnosticsReceiver; +import lombok.core.PostCompilerTransformation; +import lombok.spi.Provides; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.InvokeDynamicInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; +import org.objectweb.asm.tree.analysis.AnalyzerException; +import org.objectweb.asm.tree.analysis.BasicInterpreter; +import org.objectweb.asm.tree.analysis.BasicValue; +import org.objectweb.asm.tree.analysis.Frame; + +@Provides +public class GeneratorPostTransformation implements PostCompilerTransformation { + private static final String GENERATOR_ERR_RESUME_ON_FINISH = "Called next on finished generator"; + private static final String GENERATOR_ERR_UNREACHABLE = "Unreachable generator state"; + + @Override public byte[] applyTransformations(byte[] original, String fileName, final DiagnosticsReceiver diagnostics) { + if (!"lombok/Lombok$Generator".equals(new ClassFileMetaData(original).getSuperClassName())) return null; + + byte[] fixedByteCode = fixJSRInlining(original); + + ClassReader reader = new ClassReader(fixedByteCode); + ClassWriter writer = new ClassWriter( + reader, + ClassWriter.COMPUTE_MAXS + ); + + GeneratorClass generatorClass = initializeClass(Type.getType(Object.class), reader.getClassName(), writer, setupTable(reader)); + + reader.accept(transformClass(writer, reader.getClassName(), generatorClass), 0); + + return writer.toByteArray(); + } + + private static boolean isYieldThis(int opcode, String className, String owner, String name) { + return opcode == Opcodes.INVOKEVIRTUAL && "yieldThis".equals(name) && owner.equals(className); + } + + private static boolean isYieldAll(int opcode, String className, String owner, String name) { + return opcode == Opcodes.INVOKEVIRTUAL && "yieldAll".equals(name) && owner.equals(className); + } + + private static void pushIntConstant(MethodVisitor visitor, int constant) { + switch (constant) { + case -1: visitor.visitInsn(Opcodes.ICONST_M1); break; + case 0: visitor.visitInsn(Opcodes.ICONST_0); break; + case 1: visitor.visitInsn(Opcodes.ICONST_1); break; + case 2: visitor.visitInsn(Opcodes.ICONST_2); break; + case 3: visitor.visitInsn(Opcodes.ICONST_3); break; + case 4: visitor.visitInsn(Opcodes.ICONST_4); break; + case 5: visitor.visitInsn(Opcodes.ICONST_5); break; + default: { + if (constant <= 0xff) { + visitor.visitIntInsn(Opcodes.BIPUSH, constant); + } else if (constant <= 0xffff) { + visitor.visitIntInsn(Opcodes.SIPUSH, constant); + } else { + visitor.visitLdcInsn(constant); + } + break; + } + } + } + + private ClassVisitor transformClass(final ClassWriter writer, final String className, final GeneratorClass generatorClass) { + return new ClassVisitor(Opcodes.ASM7, writer) { + @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + access |= Opcodes.ACC_SYNTHETIC; + superName = Type.getInternalName(Object.class); + interfaces = new String[] { + Type.getInternalName(Iterable.class), + Type.getInternalName(Iterator.class) + }; + + super.visit(version, access, name, signature, superName, interfaces); + + generatorClass.stateNode.accept(writer); + generatorClass.peekedNode.accept(writer); + generatorClass.resultNode.accept(writer); + + visitIterator(writer); + visitHasNext(writer, className, generatorClass); + visitNext(writer, className, generatorClass); + visitRemove(writer); + } + + @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions); + + if ("".equals(name)) { + return new MethodVisitor(Opcodes.ASM7, visitor) { + @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + if (opcode == Opcodes.INVOKESPECIAL && "lombok/Lombok$Generator".equals(owner)) { + owner = Type.getInternalName(Object.class); + } + + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + }; + } else if ("advance".equals(name)) { + return transformAdvance(writer, visitor, className, generatorClass); + } + + return visitor; + } + }; + } + + private MethodVisitor transformAdvance( + final ClassWriter writer, + final MethodVisitor methodVisitor, + final String className, + final GeneratorClass generatorClass + ) { + final Frame frame = new Frame(65536, 65536); + + MethodVisitor transformer = new MethodVisitor(Opcodes.ASM7, methodVisitor) { + private int index = 0; + + private int tmpIndex = 0; + private Map variableMap = new HashMap(); + + private void setState(int index) { + super.visitVarInsn(Opcodes.ALOAD, 0); + pushIntConstant(mv, index); + + super.visitFieldInsn(Opcodes.PUTFIELD, className, generatorClass.stateNode.name, generatorClass.stateNode.desc); + } + + @Override public void visitCode() { + super.visitCode(); + + Label[] labels = generatorClass.table.toLabels(); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, generatorClass.stateNode.name, generatorClass.stateNode.desc); + super.visitTableSwitchInsn(0, labels.length - 1, generatorClass.table.defaultLabel, labels); + super.visitLabel(generatorClass.table.start); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + + private Type getVariableType(int opcode, int varIndex) { + switch (opcode) { + case Opcodes.ISTORE: + case Opcodes.ILOAD: return Type.INT_TYPE; + + case Opcodes.FLOAD: + case Opcodes.FSTORE: return Type.FLOAT_TYPE; + + case Opcodes.DLOAD: + case Opcodes.DSTORE: return Type.DOUBLE_TYPE; + + case Opcodes.LLOAD: + case Opcodes.LSTORE: return Type.LONG_TYPE; + + default: { + BasicValue val = frame.getLocal(varIndex); + if (val != null) { + return val.getType(); + } + + return Type.getType(Object.class); + } + } + } + + private FieldNode createTempVariable(String descriptor) { + FieldNode node = new FieldNode(Opcodes.ACC_SYNTHETIC, "gen$" + tmpIndex++, descriptor, null, null); + writer.visitField(node.access, node.name, descriptor, null, null); + return node; + } + + @Override public void visitVarInsn(int opcode, int varIndex) { + switch (opcode) { + case Opcodes.ASTORE: + case Opcodes.ISTORE: + case Opcodes.LSTORE: + case Opcodes.FSTORE: + case Opcodes.DSTORE: { + String descriptor = getVariableType(opcode, varIndex).getDescriptor(); + + super.visitVarInsn(Opcodes.ALOAD, 0); + + FieldNode old = variableMap.get(varIndex); + FieldNode node; + if (old != null && descriptor.equals(old.desc)) { + node = old; + } else { + if (old != null && Type.getType(old.desc).getSort() == Type.OBJECT) { + super.visitInsn(Opcodes.DUP); + super.visitInsn(Opcodes.ACONST_NULL); + super.visitFieldInsn(Opcodes.PUTFIELD, className, old.name, old.desc); + } + + node = createTempVariable(descriptor); + variableMap.put(varIndex, node); + } + + super.visitInsn(Opcodes.SWAP); + super.visitFieldInsn(Opcodes.PUTFIELD, className, node.name, descriptor); + return; + } + + case Opcodes.ALOAD: + case Opcodes.ILOAD: + case Opcodes.LLOAD: + case Opcodes.FLOAD: + case Opcodes.DLOAD: { + FieldNode node = variableMap.get(varIndex); + if (node == null) { + break; + } + + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, node.name, node.desc); + return; + } + } + + super.visitVarInsn(opcode, varIndex); + } + + @Override public void visitIincInsn(int varIndex, int increment) { + FieldNode node = variableMap.get(varIndex); + if (node == null) { + super.visitIincInsn(varIndex, increment); + return; + } + + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn(Opcodes.GETFIELD, className, node.name, node.desc); + pushIntConstant(mv, increment); + super.visitInsn(Opcodes.IADD); + + super.visitFieldInsn(Opcodes.PUTFIELD, className, node.name, node.desc); + } + + @Override public void visitFrame(int type, int numLocal, Object[] local, int numStack, Object[] stack) { + if (local != null) { + // Remove all local variables + if (type == Opcodes.F_FULL || type == Opcodes.F_NEW) { + numLocal = 1; + } else if (type == Opcodes.F_APPEND || type == Opcodes.F_CHOP) { + numLocal = 0; + } + } + + super.visitFrame(type, numLocal, local, numStack, stack); + } + + private void expandYieldThis() { + Label next = generatorClass.table.yields[index++]; + + // Assign next value (value already loaded by method invocation) + super.visitFieldInsn(Opcodes.PUTFIELD, className, generatorClass.resultNode.name, generatorClass.resultNode.desc); + // Assign state + setState(index); + super.visitInsn(Opcodes.RETURN); + + // Mark next + super.visitLabel(next); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + } + + private void expandYieldAllIterable() { + Label next = generatorClass.table.yields[index++]; + + FieldNode iteratorNode = createTempVariable(Type.getDescriptor(Iterator.class)); + + // Create iterator (iterable already loaded by method invocation) + super.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getInternalName(Iterable.class), + "iterator", + "()Ljava/util/Iterator;", + true + ); + super.visitFieldInsn(Opcodes.PUTFIELD, className, iteratorNode.name, iteratorNode.desc); + + // Mark next + setState(index); + super.visitLabel(next); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Jump to next on end + Label loopEnd = new Label(); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, iteratorNode.name, iteratorNode.desc); + super.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getInternalName(Iterator.class), + "hasNext", + "()Z", + true + ); + super.visitJumpInsn(Opcodes.IFEQ, loopEnd); + + // Assign next value + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn(Opcodes.GETFIELD, className, iteratorNode.name, iteratorNode.desc); + super.visitMethodInsn( + Opcodes.INVOKEINTERFACE, + Type.getInternalName(Iterator.class), + "next", + "()Ljava/lang/Object;", + true + ); + super.visitFieldInsn(Opcodes.PUTFIELD, className, generatorClass.resultNode.name, generatorClass.resultNode.desc); + super.visitInsn(Opcodes.RETURN); + + super.visitLabel(loopEnd); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Cleanup iterator + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.ACONST_NULL); + super.visitFieldInsn(Opcodes.PUTFIELD, className, iteratorNode.name, iteratorNode.desc); + } + + private void expandYieldAllArray() { + Label next = generatorClass.table.yields[index++]; + + FieldNode arrayNode = createTempVariable("[" + generatorClass.type.getDescriptor()); + FieldNode lengthNode = createTempVariable(Type.INT_TYPE.getDescriptor()); + FieldNode incrementNode = createTempVariable(Type.INT_TYPE.getDescriptor()); + Label loopEnd = new Label(); + + // Initialize array + super.visitInsn(Opcodes.DUP2); + super.visitFieldInsn(Opcodes.PUTFIELD, className, arrayNode.name, arrayNode.desc); + + // Initialize length + super.visitInsn(Opcodes.ARRAYLENGTH); + super.visitFieldInsn(Opcodes.PUTFIELD, className, lengthNode.name, lengthNode.desc); + + // Initialize increment + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.ICONST_0); + super.visitFieldInsn(Opcodes.PUTFIELD, className, incrementNode.name, incrementNode.desc); + + // Mark next + setState(index); + super.visitLabel(next); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Check increment + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, incrementNode.name, incrementNode.desc); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, lengthNode.name, lengthNode.desc); + super.visitJumpInsn(Opcodes.IF_ICMPGE, loopEnd); + + // get value from array + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn(Opcodes.GETFIELD, className, arrayNode.name, arrayNode.desc); + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitFieldInsn(Opcodes.GETFIELD, className, incrementNode.name, incrementNode.desc); + super.visitInsn(Opcodes.AALOAD); + super.visitFieldInsn(Opcodes.PUTFIELD, className, generatorClass.resultNode.name, generatorClass.resultNode.desc); + + // add increment by 1 and return + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.DUP); + super.visitFieldInsn(Opcodes.GETFIELD, className, incrementNode.name, incrementNode.desc); + super.visitInsn(Opcodes.ICONST_1); + super.visitInsn(Opcodes.IADD); + super.visitFieldInsn(Opcodes.PUTFIELD, className, incrementNode.name, incrementNode.desc); + super.visitInsn(Opcodes.RETURN); + + super.visitLabel(loopEnd); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + + // Cleanup array + super.visitVarInsn(Opcodes.ALOAD, 0); + super.visitInsn(Opcodes.ACONST_NULL); + super.visitFieldInsn(Opcodes.PUTFIELD, className, arrayNode.name, arrayNode.desc); + } + + @Override public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + if (isYieldThis(opcode, className, owner, name)) { + expandYieldThis(); + return; + } else if (isYieldAll(opcode, className, owner, name)) { + Type[] arguments = Type.getArgumentTypes(descriptor); + if (arguments[0].getSort() != Type.ARRAY) { + expandYieldAllIterable(); + } else { + expandYieldAllArray(); + } + return; + } + + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override public void visitInsn(int opcode) { + switch(opcode) { + // Mark generator finished on return + case Opcodes.IRETURN: + case Opcodes.FRETURN: + case Opcodes.ARETURN: + case Opcodes.LRETURN: + case Opcodes.DRETURN: + case Opcodes.RETURN: { + setState(generatorClass.table.getFinishState()); + break; + } + + default: break; + } + + super.visitInsn(opcode); + } + + @Override public void visitMaxs(int maxStack, int maxLocals) { + Label finish = new Label(); + super.visitLabel(finish); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + setState(generatorClass.table.getFinishState()); + + super.visitLabel(generatorClass.table.end); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + super.visitInsn(Opcodes.RETURN); + + Label exceptionCleanup = new Label(); + super.visitLabel(exceptionCleanup); + super.visitFrame(Opcodes.F_SAME1, 0, null, 1, new Object[] {"java/lang/Throwable"}); + setState(generatorClass.table.getFinishState()); + super.visitInsn(Opcodes.ATHROW); + + super.visitTryCatchBlock(generatorClass.table.start, finish, exceptionCleanup, null); + + // Default case + super.visitLabel(generatorClass.table.defaultLabel); + super.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + super.visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException"); + super.visitInsn(Opcodes.DUP); + super.visitLdcInsn(GENERATOR_ERR_UNREACHABLE); + super.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", "", "(Ljava/lang/String;)V", false); + super.visitInsn(Opcodes.ATHROW); + + super.visitMaxs(maxStack, maxLocals); + } + }; + + return new FrameTracker(Opcodes.ASM7, frame, transformer); + } + + private GeneratorClass initializeClass( + Type type, + String className, + ClassWriter writer, + GeneratorTable table + ) { + FieldNode stateNode = new FieldNode(Opcodes.ACC_SYNTHETIC, "$state", "I", null, null); + FieldNode peekedNode = new FieldNode(Opcodes.ACC_SYNTHETIC, "$peeked", "Z", null, null); + FieldNode resultNode = new FieldNode(Opcodes.ACC_SYNTHETIC, "$result", type.getDescriptor(), null, null); + + return new GeneratorClass(type, stateNode, peekedNode, resultNode, table); + } + + private void visitIterator(ClassVisitor classVisitor) { + MethodVisitor visitor = classVisitor.visitMethod( + Opcodes.ACC_PUBLIC, + "iterator", + "()" + Type.getDescriptor(Iterator.class), + null, + null + ); + visitor.visitCode(); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitInsn(Opcodes.ARETURN); + visitor.visitMaxs(1, 1); + visitor.visitEnd(); + } + + private void visitHasNext(ClassVisitor classVisitor, String className, GeneratorClass generatorClass) { + FieldNode stateNode = generatorClass.stateNode; + FieldNode peekedNode = generatorClass.peekedNode; + + MethodVisitor visitor = classVisitor.visitMethod( + Opcodes.ACC_PUBLIC, + "hasNext", + "()Z", + null, + null + ); + visitor.visitCode(); + + Label advanceLabel = new Label(); + Label returnFalse = new Label(); + + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitFieldInsn(Opcodes.GETFIELD, className, peekedNode.name, peekedNode.desc); + visitor.visitJumpInsn(Opcodes.IFEQ, advanceLabel); + + visitor.visitInsn(Opcodes.ICONST_1); + visitor.visitInsn(Opcodes.IRETURN); + + visitor.visitLabel(advanceLabel); + visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, "advance", "()V", false); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitFieldInsn(Opcodes.GETFIELD, className, stateNode.name, stateNode.desc); + pushIntConstant(visitor, generatorClass.table.getFinishState()); + visitor.visitJumpInsn(Opcodes.IF_ICMPEQ, returnFalse); + + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitInsn(Opcodes.ICONST_1); + visitor.visitFieldInsn(Opcodes.PUTFIELD, className, peekedNode.name, peekedNode.desc); + visitor.visitInsn(Opcodes.ICONST_1); + visitor.visitInsn(Opcodes.IRETURN); + + visitor.visitLabel(returnFalse); + visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + visitor.visitInsn(Opcodes.ICONST_0); + visitor.visitInsn(Opcodes.IRETURN); + + visitor.visitMaxs(2, 1); + visitor.visitEnd(); + } + + private void visitNext(ClassVisitor classVisitor, String className, GeneratorClass generatorClass) { + FieldNode stateNode = generatorClass.stateNode; + FieldNode peekedNode = generatorClass.peekedNode; + FieldNode resultNode = generatorClass.resultNode; + + MethodVisitor visitor = classVisitor.visitMethod( + Opcodes.ACC_PUBLIC, + "next", + "()Ljava/lang/Object;", + null, + null + ); + visitor.visitCode(); + Label throwLabel = new Label(); + Label returnLabel = new Label(); + Label advanceLabel = new Label(); + + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitFieldInsn(Opcodes.GETFIELD, className, peekedNode.name, peekedNode.desc); + visitor.visitJumpInsn(Opcodes.IFEQ, advanceLabel); + + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitInsn(Opcodes.ICONST_0); + visitor.visitFieldInsn(Opcodes.PUTFIELD, className, peekedNode.name, peekedNode.desc); + visitor.visitJumpInsn(Opcodes.GOTO, returnLabel); + + visitor.visitLabel(advanceLabel); + visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, className, "advance", "()V", false); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitFieldInsn(Opcodes.GETFIELD, className, stateNode.name, stateNode.desc); + pushIntConstant(visitor, generatorClass.table.getFinishState()); + visitor.visitJumpInsn(Opcodes.IF_ICMPEQ, throwLabel); + + visitor.visitLabel(returnLabel); + visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitFieldInsn(Opcodes.GETFIELD, className, resultNode.name, resultNode.desc); + visitor.visitVarInsn(Opcodes.ALOAD, 0); + visitor.visitInsn(Opcodes.ACONST_NULL); + visitor.visitFieldInsn(Opcodes.PUTFIELD, className, resultNode.name, resultNode.desc); + visitor.visitInsn(Opcodes.ARETURN); + + visitor.visitLabel(throwLabel); + visitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null); + String exceptionClass = Type.getInternalName(NoSuchElementException.class); + visitor.visitTypeInsn(Opcodes.NEW, exceptionClass); + visitor.visitInsn(Opcodes.DUP); + visitor.visitLdcInsn(GENERATOR_ERR_RESUME_ON_FINISH); + visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionClass, "", "(Ljava/lang/String;)V", false); + visitor.visitInsn(Opcodes.ATHROW); + + visitor.visitMaxs(3, 1); + visitor.visitEnd(); + } + + private void visitRemove(ClassVisitor classVisitor) { + MethodVisitor visitor = classVisitor.visitMethod( + Opcodes.ACC_PUBLIC, + "remove", + "()V", + null, + null + ); + visitor.visitCode(); + + String exceptionClass = Type.getInternalName(UnsupportedOperationException.class); + visitor.visitTypeInsn(Opcodes.NEW, exceptionClass); + visitor.visitInsn(Opcodes.DUP); + visitor.visitLdcInsn("remove"); + visitor.visitMethodInsn(Opcodes.INVOKESPECIAL, exceptionClass, "", "(Ljava/lang/String;)V", false); + visitor.visitInsn(Opcodes.ATHROW); + + visitor.visitMaxs(3, 1); + visitor.visitEnd(); + } + + private GeneratorTable setupTable(ClassReader reader) { + final String className = reader.getClassName(); + final List