From 44e2e7421690afb34fb825bcfd9ed672e60ec454 Mon Sep 17 00:00:00 2001 From: Daniel Mangold Date: Sat, 30 Nov 2024 12:55:55 +0100 Subject: [PATCH] Create separate exception for irrecoverable header mismatches --- .../transform/SubmissionClassVisitor.java | 17 ++-- .../transform/SubmissionMethodVisitor.java | 6 +- .../algoutils/transform/util/Constants.java | 1 + .../util/IncompatibleHeaderException.java | 78 +++++++++++++++++++ .../transform/util/TransformationUtils.java | 14 ---- 5 files changed, 87 insertions(+), 29 deletions(-) create mode 100644 tutor/src/main/java/org/tudalgo/algoutils/transform/util/IncompatibleHeaderException.java 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 0de8ce6..58c4930 100644 --- a/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionClassVisitor.java @@ -162,25 +162,20 @@ public MethodVisitor visitMethod(int access, String name, String descriptor, Str MethodHeader computedMethodHeader = submissionClassInfo.getComputedMethodHeader(name, descriptor); if (!TransformationUtils.contextIsCompatible(access, computedMethodHeader.access())) { - MethodHeader methodHeader = new MethodHeader(computedMethodHeader.owner(), + computedMethodHeader = new MethodHeader(computedMethodHeader.owner(), access, name + "$submission", transformationContext.toComputedType(descriptor).getDescriptor(), signature, exceptions); - return new SubmissionMethodVisitor(methodHeader.toMethodVisitor(getDelegate()), - transformationContext, - submissionClassInfo, - originalMethodHeader, - methodHeader); } else { visitedMethods.add(computedMethodHeader); - return new SubmissionMethodVisitor(computedMethodHeader.toMethodVisitor(getDelegate()), - transformationContext, - submissionClassInfo, - originalMethodHeader, - computedMethodHeader); } + return new SubmissionMethodVisitor(computedMethodHeader.toMethodVisitor(getDelegate()), + transformationContext, + submissionClassInfo, + originalMethodHeader, + computedMethodHeader); } } diff --git a/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionMethodVisitor.java b/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionMethodVisitor.java index 1f5a427..dbb4203 100644 --- a/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionMethodVisitor.java +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/SubmissionMethodVisitor.java @@ -327,10 +327,8 @@ public void visitCode() { } if (headerMismatch) { - TransformationUtils.buildExceptionForHeaderMismatch(getDelegate(), - "Method has incorrect return or parameter types", - computedMethodHeader, - originalMethodHeader); + new IncompatibleHeaderException("Method has incorrect return or parameter types", computedMethodHeader, originalMethodHeader) + .replicateInBytecode(getDelegate(), true); } else { // visit original code super.visitCode(); diff --git a/tutor/src/main/java/org/tudalgo/algoutils/transform/util/Constants.java b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/Constants.java index c657b58..dde6344 100644 --- a/tutor/src/main/java/org/tudalgo/algoutils/transform/util/Constants.java +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/Constants.java @@ -19,6 +19,7 @@ public final class Constants { public static final Type STRING_ARRAY_TYPE = Type.getType(String[].class); public static final Type SET_TYPE = Type.getType(Set.class); + public static final Type HEADER_TYPE = Type.getType(Header.class); public static final Type CLASS_HEADER_TYPE = Type.getType(ClassHeader.class); public static final Type FIELD_HEADER_TYPE = Type.getType(FieldHeader.class); public static final Type METHOD_HEADER_TYPE = Type.getType(MethodHeader.class); diff --git a/tutor/src/main/java/org/tudalgo/algoutils/transform/util/IncompatibleHeaderException.java b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/IncompatibleHeaderException.java new file mode 100644 index 0000000..ab42758 --- /dev/null +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/IncompatibleHeaderException.java @@ -0,0 +1,78 @@ +package org.tudalgo.algoutils.transform.util; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Type; + +import static org.objectweb.asm.Opcodes.*; +import static org.objectweb.asm.Opcodes.ATHROW; + +/** + * Thrown to indicate that a class, field or method (including constructors) was declared incorrectly. + *

+ * For fields, this means that the field was declared static in the submission class while not being + * declared static in the solution class, or vice versa. + *

+ *

+ * For methods, it may indicate the same problem as for fields. + * It may also indicate that methods or constructors have the wrong number of parameters. + * Lastly, it may indicate that the return type or parameter types are incompatible, e.g., + * a submission class' method returns a primitive type while the solution class' method returns + * a reference type. + *

+ * + * @author Daniel Mangold + */ +public class IncompatibleHeaderException extends RuntimeException { + + private final String message; + private final Header expected; + private final Header actual; + + /** + * Constructs a new {@link IncompatibleHeaderException} instance. + * + * @param message the exception message + * @param expected the expected header + * @param actual the actual header + */ + public IncompatibleHeaderException(String message, Header expected, Header actual) { + super(); + this.message = message; + this.expected = expected; + this.actual = actual; + } + + @Override + public String getMessage() { + return "%s%nExpected: %s%nActual: %s%n".formatted(message, expected, actual); + } + + /** + * Replicates this exception in bytecode and optionally throws it. + * If it is not thrown, a reference to the newly created instance is located at the top + * of the method visitor's stack upon return. + * + * @param mv the method visitor to use + * @param throwException whether the exception should be thrown + * @return the maximum stack size used + */ + public int replicateInBytecode(MethodVisitor mv, boolean throwException) { + int maxStack, stackSize; + + mv.visitTypeInsn(NEW, Type.getInternalName(getClass())); + mv.visitInsn(DUP); + mv.visitLdcInsn(message); + maxStack = stackSize = 3; + maxStack = Math.max(maxStack, stackSize++ + expected.buildHeader(mv)); + maxStack = Math.max(maxStack, stackSize + actual.buildHeader(mv)); + mv.visitMethodInsn(INVOKESPECIAL, + Type.getInternalName(getClass()), + "", + Type.getMethodDescriptor(Type.VOID_TYPE, Constants.STRING_TYPE, Constants.HEADER_TYPE, Constants.HEADER_TYPE), + false); + if (throwException) { + mv.visitInsn(ATHROW); + } + return maxStack; + } +} diff --git a/tutor/src/main/java/org/tudalgo/algoutils/transform/util/TransformationUtils.java b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/TransformationUtils.java index b0e77cf..5eb4578 100644 --- a/tutor/src/main/java/org/tudalgo/algoutils/transform/util/TransformationUtils.java +++ b/tutor/src/main/java/org/tudalgo/algoutils/transform/util/TransformationUtils.java @@ -217,20 +217,6 @@ public static int buildArray(MethodVisitor mv, Type componentType, Object[] arra return maxStack; } - public static void buildExceptionForHeaderMismatch(MethodVisitor mv, String message, Header expected, Header actual) { - mv.visitTypeInsn(NEW, Type.getInternalName(AssertionFailedError.class)); - mv.visitInsn(DUP); - mv.visitLdcInsn(message); - expected.buildHeader(mv); - actual.buildHeader(mv); - mv.visitMethodInsn(INVOKESPECIAL, - Type.getInternalName(AssertionFailedError.class), - "", - Type.getMethodDescriptor(Type.VOID_TYPE, Constants.STRING_TYPE, Constants.OBJECT_TYPE, Constants.OBJECT_TYPE), - false); - mv.visitInsn(ATHROW); - } - /** * Returns a human-readable form of the given modifiers. *