From c545b48e8e8ffa5fa9264943f68dedbd84f30ad9 Mon Sep 17 00:00:00 2001 From: Daniel Mangold Date: Mon, 18 Nov 2024 23:25:26 +0100 Subject: [PATCH] Implement constructor substitution --- .../transform/SubmissionClassVisitor.java | 181 +++++++++++++++++- 1 file changed, 179 insertions(+), 2 deletions(-) diff --git a/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java b/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java index 0286587..ddfaefa 100644 --- a/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java @@ -6,7 +6,9 @@ import org.objectweb.asm.tree.MethodNode; import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; +import java.util.stream.Stream; import static org.tudalgo.algoutils.transform.util.TransformationUtils.*; import static org.objectweb.asm.Opcodes.*; @@ -155,6 +157,7 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str Type submissionExecutionHandlerType = SubmissionExecutionHandler.INTERNAL_TYPE; Type sehInternalType = SubmissionExecutionHandler.Internal.INTERNAL_TYPE; Type methodSubstitutionType = MethodSubstitution.INTERNAL_TYPE; + Type constructorBehaviourType = MethodSubstitution.ConstructorBehaviour.INTERNAL_TYPE; MethodHeader methodHeader = submissionClassInfo.getComputedMethodHeader(name, descriptor); visitedMethods.add(methodHeader); @@ -165,7 +168,8 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str int nextLocalsIndex = (Type.getArgumentsAndReturnSizes(methodHeader.descriptor()) >> 2) - (isStatic ? 1 : 0); int submissionExecutionHandlerIndex = nextLocalsIndex++; int methodHeaderIndex = nextLocalsIndex++; - int methodSubstitutionIndex = nextLocalsIndex; + int methodSubstitutionIndex = nextLocalsIndex++; + int constructorBehaviourIndex = nextLocalsIndex; // calculate default locals for frames List fullFrameLocals = Arrays.stream(Type.getArgumentTypes(methodHeader.descriptor())) @@ -256,7 +260,7 @@ public void visitCode() { } // Method substitution - if (!isConstructor) { + { // check if substitution exists for this method super.visitFrame(F_SAME, 0, null, 0, null); super.visitLabel(substitutionCheckLabel); @@ -282,6 +286,106 @@ public void visitCode() { fullFrameLocals.add(methodSubstitutionType.getInternalName()); super.visitLabel(substitutionStartLabel); + if (isConstructor) { + List superConstructors = submissionClassInfo.getOriginalSuperClassConstructorHeaders() + .stream() + .map(mh -> submissionClassInfo.getComputedSuperClassConstructorHeader(mh.descriptor())) + .toList(); + List constructors = submissionClassInfo.getOriginalMethodHeaders() + .stream() + .filter(mh -> mh.name().equals("")) + .map(mh -> submissionClassInfo.getComputedMethodHeader(mh.name(), mh.descriptor())) + .toList(); + Label[] labels = Stream.generate(Label::new) + .limit(superConstructors.size() + constructors.size() + 1) + .toArray(Label[]::new); + Label substitutionExecuteLabel = new Label(); + AtomicInteger labelIndex = new AtomicInteger(); + + /* + * Representation in source code: + * MethodSubstitution.ConstructorBehaviour cb = methodSubstitution.getConstructorBehaviour(); + * if (cb.owner().equals() && cb.descriptor().equals()) { + * super(...); + * } else if ... // for every superclass constructor + * else if (cb.owner().equals() && cb.descriptor().equals()) { + * this(...); + * } else if ... // for every regular constructor + * else { + * throw new IllegalArgumentException(...); + * } + */ + super.visitVarInsn(ALOAD, methodSubstitutionIndex); + super.visitMethodInsn(INVOKEINTERFACE, + methodSubstitutionType.getInternalName(), + "getConstructorBehaviour", + Type.getMethodDescriptor(constructorBehaviourType), + true); + super.visitVarInsn(ASTORE, constructorBehaviourIndex); + super.visitFrame(F_APPEND, 1, new Object[] {constructorBehaviourType.getInternalName()}, 0, null); + fullFrameLocals.add(constructorBehaviourType.getInternalName()); + super.visitLabel(labels[0]); + for (MethodHeader superConstructorHeader : superConstructors) { + buildConstructorInvocationBranch(superConstructorHeader, substitutionExecuteLabel, labels, labelIndex); + } + for (MethodHeader constructorHeader : constructors) { + buildConstructorInvocationBranch(constructorHeader, substitutionExecuteLabel, labels, labelIndex); + } + + // if no matching constructor was found, throw an IllegalArgumentException + { + Type illegalArgumentExceptionType = Type.getType(IllegalArgumentException.class); + super.visitTypeInsn(NEW, illegalArgumentExceptionType.getInternalName()); + super.visitInsn(DUP); + + super.visitLdcInsn("No matching constructor was found for owner %s and descriptor %s"); + super.visitInsn(ICONST_2); + super.visitTypeInsn(ANEWARRAY, stringType.getInternalName()); + super.visitInsn(DUP); + super.visitInsn(ICONST_0); + super.visitVarInsn(ALOAD, constructorBehaviourIndex); + super.visitMethodInsn(INVOKEVIRTUAL, + constructorBehaviourType.getInternalName(), + "owner", + Type.getMethodDescriptor(stringType), + false); + super.visitInsn(AASTORE); + super.visitInsn(DUP); + super.visitInsn(ICONST_1); + super.visitVarInsn(ALOAD, constructorBehaviourIndex); + super.visitMethodInsn(INVOKEVIRTUAL, + constructorBehaviourType.getInternalName(), + "descriptor", + Type.getMethodDescriptor(stringType), + false); + super.visitInsn(AASTORE); + super.visitMethodInsn(INVOKEVIRTUAL, + stringType.getInternalName(), + "formatted", + Type.getMethodDescriptor(stringType, objectArrayType), + false); + + super.visitMethodInsn(INVOKESPECIAL, + illegalArgumentExceptionType.getInternalName(), + "", + Type.getMethodDescriptor(Type.VOID_TYPE, stringType), + false); + super.visitInsn(ATHROW); + } + + fullFrameLocals.removeLast(); + List locals = new ArrayList<>(fullFrameLocals); + locals.set(0, className); + super.visitFrame(F_FULL, locals.size(), locals.toArray(), 0, null); + super.visitLabel(substitutionExecuteLabel); + super.visitLocalVariable("constructorBehaviour", + constructorBehaviourType.getDescriptor(), + null, + labels[labelIndex.get()], + substitutionExecuteLabel, + constructorBehaviourIndex); + } + super.visitVarInsn(ALOAD, methodSubstitutionIndex); buildInvocation(Type.getArgumentTypes(methodHeader.descriptor())); super.visitMethodInsn(INVOKEINTERFACE, @@ -366,6 +470,79 @@ public void visitCode() { super.visitCode(); } + private void buildConstructorInvocationBranch(MethodHeader constructorHeader, + Label substitutionExecuteLabel, + Label[] labels, + AtomicInteger labelIndex) { + super.visitVarInsn(ALOAD, constructorBehaviourIndex); + super.visitMethodInsn(INVOKEVIRTUAL, + constructorBehaviourType.getInternalName(), + "owner", + Type.getMethodDescriptor(stringType), + false); + super.visitLdcInsn(constructorHeader.owner()); + super.visitMethodInsn(INVOKEVIRTUAL, + stringType.getInternalName(), + "equals", + Type.getMethodDescriptor(Type.BOOLEAN_TYPE, objectType), + false); + + super.visitVarInsn(ALOAD, constructorBehaviourIndex); + super.visitMethodInsn(INVOKEVIRTUAL, + constructorBehaviourType.getInternalName(), + "descriptor", + Type.getMethodDescriptor(stringType), + false); + super.visitLdcInsn(constructorHeader.descriptor()); + super.visitMethodInsn(INVOKEVIRTUAL, + stringType.getInternalName(), + "equals", + Type.getMethodDescriptor(Type.BOOLEAN_TYPE, objectType), + false); + + super.visitInsn(IAND); + super.visitJumpInsn(IFEQ, labels[labelIndex.get() + 1]); // jump to next branch if false + + Label argsVarStartLabel = new Label(); + super.visitVarInsn(ALOAD, constructorBehaviourIndex); + super.visitMethodInsn(INVOKEVIRTUAL, + constructorBehaviourType.getInternalName(), + "args", + Type.getMethodDescriptor(objectArrayType), + false); + super.visitVarInsn(ASTORE, constructorBehaviourIndex + 1); + super.visitFrame(F_APPEND, 1, new Object[] {objectArrayType.getInternalName()}, 0, null); + fullFrameLocals.add(objectArrayType.getInternalName()); + super.visitLabel(argsVarStartLabel); + + super.visitVarInsn(ALOAD, 0); + Type[] parameterTypes = Type.getArgumentTypes(constructorHeader.descriptor()); + for (int i = 0; i < parameterTypes.length; i++) { // unpack array + super.visitVarInsn(ALOAD, constructorBehaviourIndex + 1); + super.visitIntInsn(SIPUSH, i); + super.visitInsn(AALOAD); + unboxType(getDelegate(), parameterTypes[i]); + } + super.visitMethodInsn(INVOKESPECIAL, + constructorHeader.owner(), + "", + constructorHeader.descriptor(), + false); + super.visitJumpInsn(GOTO, substitutionExecuteLabel); + + fullFrameLocals.removeLast(); +// List locals = new ArrayList<>(fullFrameLocals); +// locals.set(0, className); + super.visitFrame(F_CHOP, 1, null, 0, new Object[0]); + super.visitLabel(labels[labelIndex.incrementAndGet()]); + super.visitLocalVariable("args", + objectArrayType.getDescriptor(), + null, + argsVarStartLabel, + labels[labelIndex.get()], + constructorBehaviourIndex + 1); + } + @Override public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { // skip transformation if only default transformations are applied or owner is not part of the submission