-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add prototype for full project instrumentation
- Loading branch information
Showing
16 changed files
with
2,553 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
student/src/main/java/org/tudalgo/algoutils/student/annotation/ForceSignature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.tudalgo.algoutils.student.annotation; | ||
|
||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
/** | ||
* Forces the annotated type or member to be mapped to the specified one. | ||
* Mappings must be 1:1, meaning multiple annotated members (or types) may not map to the same target and | ||
* all members and types may be targeted by at most one annotation. | ||
* | ||
* @author Daniel Mangold | ||
*/ | ||
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.METHOD}) | ||
@Retention(RetentionPolicy.CLASS) | ||
public @interface ForceSignature { | ||
|
||
/** | ||
* The identifier of the annotated type / member. | ||
* The value must be as follows: | ||
* <ul> | ||
* <li>Types: the fully qualified name of the type (e.g., {@code java.lang.Object})</li> | ||
* <li>Fields: the name / identifier of the field</li> | ||
* <li>Constructors: Always {@code <init>}, regardless of the class</li> | ||
* <li>Methods: the name / identifier of the method</li> | ||
* </ul> | ||
* | ||
* @return the type / member identifier | ||
*/ | ||
String identifier(); | ||
|
||
/** | ||
* The method descriptor as specified by | ||
* <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.3">Chapter 4.3</a> | ||
* of the Java Virtual Machine Specification. | ||
* If a value is set, it takes precedence over {@link #returnType()} and {@link #parameterTypes()}. | ||
* | ||
* <p> | ||
* Note: Setting this value has no effect for types or fields. | ||
* </p> | ||
* | ||
* @return the method's descriptor | ||
*/ | ||
String descriptor() default ""; | ||
|
||
/** | ||
* The class object specifying the method's return type. | ||
* If a value is set, it will be overwritten if {@link #descriptor()} is also set. | ||
* Default is no return type (void). | ||
* | ||
* <p> | ||
* Note: Setting this value has no effect for types or fields. | ||
* </p> | ||
* | ||
* @return the method's return type | ||
*/ | ||
Class<?> returnType() default void.class; | ||
|
||
/** | ||
* An array of class objects specifying the method's parameter types. | ||
* The classes need to be given in the same order as they are declared by the targeted method. | ||
* If a value is set, it will be overwritten if {@link #descriptor()} is also set. | ||
* Default is no parameters. | ||
* | ||
* <p> | ||
* Note: Setting this value has no effect for types or fields. | ||
* </p> | ||
* | ||
* @return the method's parameter types | ||
*/ | ||
Class<?>[] parameterTypes() default {}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
tutor/src/main/java/org/tudalgo/algoutils/transform/SolutionClassNode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
package org.tudalgo.algoutils.transform; | ||
|
||
import org.tudalgo.algoutils.transform.util.ClassHeader; | ||
import org.tudalgo.algoutils.transform.util.FieldHeader; | ||
import org.tudalgo.algoutils.transform.util.MethodHeader; | ||
import org.objectweb.asm.FieldVisitor; | ||
import org.objectweb.asm.Handle; | ||
import org.objectweb.asm.MethodVisitor; | ||
import org.objectweb.asm.Opcodes; | ||
import org.objectweb.asm.tree.ClassNode; | ||
import org.objectweb.asm.tree.FieldNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
|
||
import java.util.Arrays; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; | ||
import static org.objectweb.asm.Opcodes.ASM9; | ||
|
||
/** | ||
* A class node for recording bytecode instructions of solution classes. | ||
* @author Daniel Mangold | ||
*/ | ||
public class SolutionClassNode extends ClassNode { | ||
|
||
private final String className; | ||
private ClassHeader classHeader; | ||
private final Map<FieldHeader, FieldNode> fields = new HashMap<>(); | ||
private final Map<MethodHeader, MethodNode> methods = new HashMap<>(); | ||
|
||
/** | ||
* Constructs a new {@link SolutionClassNode} instance. | ||
* | ||
* @param className the name of the solution class | ||
*/ | ||
public SolutionClassNode(String className) { | ||
super(Opcodes.ASM9); | ||
this.className = className; | ||
} | ||
|
||
public ClassHeader getClassHeader() { | ||
return classHeader; | ||
} | ||
|
||
/** | ||
* Returns the mapping of field headers to field nodes for this solution class. | ||
* | ||
* @return the field header => field node mapping | ||
*/ | ||
public Map<FieldHeader, FieldNode> getFields() { | ||
return fields; | ||
} | ||
|
||
/** | ||
* Returns the mapping of method headers to method nodes for this solution class. | ||
* | ||
* @return the method header => method node mapping | ||
*/ | ||
public Map<MethodHeader, MethodNode> getMethods() { | ||
return methods; | ||
} | ||
|
||
@Override | ||
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { | ||
classHeader = new ClassHeader(access, name, signature, superName, interfaces); | ||
super.visit(version, access, name, signature, superName, interfaces); | ||
} | ||
|
||
@Override | ||
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { | ||
FieldHeader fieldHeader = new FieldHeader(className, access, name, descriptor, signature); | ||
FieldNode fieldNode = (FieldNode) super.visitField(access, name, descriptor, signature, value); | ||
fields.put(fieldHeader, fieldNode); | ||
return fieldNode; | ||
} | ||
|
||
@Override | ||
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
MethodNode methodNode; | ||
if ((access & ACC_SYNTHETIC) != 0 && name.startsWith("lambda$")) { // if method is lambda | ||
methodNode = getMethodNode(access, name + "$solution", descriptor, signature, exceptions); | ||
} else { | ||
methodNode = getMethodNode(access, name, descriptor, signature, exceptions); | ||
methods.put(new MethodHeader(className, access, name, descriptor, signature, exceptions), methodNode); | ||
} | ||
return methodNode; | ||
} | ||
|
||
/** | ||
* Constructs a new method node with the given information. | ||
* The returned method node ensures that lambda methods of the solution class don't interfere | ||
* with the ones defined in the submission class. | ||
* | ||
* @param access the method's modifiers | ||
* @param name the method's name | ||
* @param descriptor the method's descriptor | ||
* @param signature the method's signature | ||
* @param exceptions the method's declared exceptions | ||
* @return a new {@link MethodNode} | ||
*/ | ||
private MethodNode getMethodNode(int access, String name, String descriptor, String signature, String[] exceptions) { | ||
return new MethodNode(ASM9, access, name, descriptor, signature, exceptions) { | ||
@Override | ||
public void visitMethodInsn(int opcodeAndSource, String owner, String name, String descriptor, boolean isInterface) { | ||
super.visitMethodInsn(opcodeAndSource, | ||
owner, | ||
name + (name.startsWith("lambda$") ? "$solution" : ""), | ||
descriptor, | ||
isInterface); | ||
} | ||
|
||
@Override | ||
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { | ||
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, Arrays.stream(bootstrapMethodArguments) | ||
.map(o -> { | ||
if (o instanceof Handle handle && handle.getName().startsWith("lambda$")) { | ||
return new Handle(handle.getTag(), | ||
handle.getOwner(), | ||
handle.getName() + "$solution", | ||
handle.getDesc(), | ||
handle.isInterface()); | ||
} else { | ||
return o; | ||
} | ||
}) | ||
.toArray()); | ||
} | ||
}; | ||
} | ||
} |
101 changes: 101 additions & 0 deletions
101
tutor/src/main/java/org/tudalgo/algoutils/transform/SolutionMergingClassTransformer.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package org.tudalgo.algoutils.transform; | ||
|
||
import org.tudalgo.algoutils.student.annotation.ForceSignature; | ||
import org.tudalgo.algoutils.transform.util.TransformationContext; | ||
import org.tudalgo.algoutils.transform.util.TransformationUtils; | ||
import org.objectweb.asm.ClassReader; | ||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.Type; | ||
import org.sourcegrade.jagr.api.testing.ClassTransformer; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
/** | ||
* A class transformer that allows logging, substitution and delegation of method invocations. | ||
* This transformer uses two source sets: solution classes and submission classes. | ||
* <br><br> | ||
* <b>Solution classes</b> | ||
* <p> | ||
* Solution classes are compiled java classes located in the {@code resources/classes/} directory. | ||
* Their class structure must match the one defined in the exercise sheet but they may define additional | ||
* members such as fields and methods. | ||
* Additional types (classes, interfaces, enums, etc.) may also be defined. | ||
* <br> | ||
* The directory structure must match the one in the output / build directory. | ||
* Due to limitations of {@link Class#getResourceAsStream(String)} the compiled classes cannot have | ||
* the {@code .class} file extension, so {@code .bin} is used instead. | ||
* For example, a class {@code MyClass} with an inner class {@code Inner} in package {@code my.package} | ||
* would be compiled to {@code my/package/MyClass.class} and {@code my/package/MyClass$Inner.class}. | ||
* In the solution classes directory they would be located at {@code resources/classes/my/package/MyClass.bin} | ||
* and {@code resources/classes/my/package/MyClass$Inner.bin}, respectively. | ||
* </p> | ||
* | ||
* <b>Submission classes</b> | ||
* <p> | ||
* Submission classes are the original java classes in the main module. | ||
* They are compiled externally and processed one by one using {@link #transform(ClassReader, ClassWriter)}. | ||
* In case classes or members are misnamed, this transformer will attempt to map them to the closest | ||
* matching solution class / member. | ||
* If both the direct and similarity matching approach fail, the intended target can be explicitly | ||
* specified using the {@link ForceSignature} annotation. | ||
* </p> | ||
* <br><br> | ||
* Implementation details: | ||
* <ul> | ||
* <li> | ||
* Unless otherwise specified, this transformer and its tools use the internal name as specified by | ||
* {@link Type#getInternalName()} when referring to class names. | ||
* </li> | ||
* <li> | ||
* The term "descriptor" refers to the bytecode-level representation of types, such as | ||
* field types, method return types or method parameter types as specified by | ||
* <a href="https://docs.oracle.com/javase/specs/jvms/se21/html/jvms-4.html#jvms-4.3">Chapter 4.3</a> | ||
* of the Java Virtual Machine Specification. | ||
* </li> | ||
* </ul> | ||
* | ||
* @see SubmissionClassVisitor | ||
* @see SubmissionExecutionHandler | ||
* @author Daniel Mangold | ||
*/ | ||
public class SolutionMergingClassTransformer implements ClassTransformer { | ||
|
||
/** | ||
* An object providing context throughout the transformation processing chain. | ||
*/ | ||
private final TransformationContext transformationContext; | ||
|
||
/** | ||
* Constructs a new {@link SolutionMergingClassTransformer} instance. | ||
* | ||
* @param projectPrefix the root package containing all submission classes, usually the sheet number | ||
* @param availableSolutionClasses the list of solution class names (fully qualified) to use | ||
*/ | ||
public SolutionMergingClassTransformer(String projectPrefix, List<String> availableSolutionClasses) { | ||
Map<String, SolutionClassNode> solutionClasses = new HashMap<>(); | ||
Map<String, SubmissionClassInfo> submissionClasses = new ConcurrentHashMap<>(); | ||
this.transformationContext = new TransformationContext(projectPrefix, solutionClasses, submissionClasses); | ||
availableSolutionClasses.stream() | ||
.map(s -> s.replace('.', '/')) | ||
.forEach(className -> solutionClasses.put(className, TransformationUtils.readSolutionClass(className))); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return SolutionMergingClassTransformer.class.getSimpleName(); | ||
} | ||
|
||
@Override | ||
public int getWriterFlags() { | ||
return ClassWriter.COMPUTE_MAXS; | ||
} | ||
|
||
@Override | ||
public void transform(ClassReader reader, ClassWriter writer) { | ||
String submissionClassName = reader.getClassName(); | ||
reader.accept(new SubmissionClassVisitor(writer, transformationContext, submissionClassName), 0); | ||
} | ||
} |
Oops, something went wrong.