diff --git a/ast/pom.xml b/ast/pom.xml index af6f6cabea..8ab119a4e4 100644 --- a/ast/pom.xml +++ b/ast/pom.xml @@ -20,14 +20,6 @@ com.github.gumtreediff core - - org.junit.jupiter - junit-jupiter-api - - - org.assertj - assertj-core - diff --git a/canonicalize/pom.xml b/canonicalize/pom.xml index 86af982020..c59de75ff5 100644 --- a/canonicalize/pom.xml +++ b/canonicalize/pom.xml @@ -15,4 +15,47 @@ UTF-8 + + + info.picocli + picocli + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 22 + 22 + --enable-preview + + + + org.apache.maven.plugins + maven-shade-plugin + + + canonicalize + + shade + + package + + canonicalize + false + + + + org.example.canonicalize.BytecodeTransformer + + + + + + + + + diff --git a/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java new file mode 100644 index 0000000000..061a1ff760 --- /dev/null +++ b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java @@ -0,0 +1,35 @@ +package org.example.canonicalize; + +import static org.example.canonicalize.BytecodeTransformerCore.writeClassfile; + +import java.io.File; +import java.util.concurrent.Callable; +import picocli.CommandLine; + +@CommandLine.Command( + name = "bytecode-transformer", + mixinStandardHelpOptions = true, + description = "Transforms Java bytecode") +public class BytecodeTransformer implements Callable { + + @CommandLine.Parameters(index = "0", description = "The Java class file to transform") + private File javaClassfile; + + @CommandLine.Option( + names = {"-o", "--output"}, + description = "The output file to write the transformed bytecode", + required = true) + private File outputFile; + + @Override + public Integer call() throws Exception { + BytecodeTransformerCore transformer = new BytecodeTransformerCore(javaClassfile); + writeClassfile(outputFile, transformer.getTransformedBytes()); + return 0; + } + + public static void main(String[] args) { + int exitCode = new CommandLine(new BytecodeTransformer()).execute(args); + System.exit(exitCode); + } +} diff --git a/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java new file mode 100644 index 0000000000..4e8d8101cc --- /dev/null +++ b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java @@ -0,0 +1,33 @@ +package org.example.canonicalize; + +import java.io.File; +import java.io.IOException; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassFileBuilder; +import java.lang.classfile.ClassModel; +import java.nio.file.Files; + +public class BytecodeTransformerCore { + private File javaClassfile; + + public BytecodeTransformerCore(File javaClassfile) { + this.javaClassfile = javaClassfile; + } + + public byte[] getTransformedBytes() throws IOException { + ClassModel bytecodeModel = readClassFile(javaClassfile); + ClassModel transformedModel = ConstantPoolTransform.transform(bytecodeModel); + + // implementation copied from java.lang.classfile.ClassTransform.ACCEPT_ALL + // ClassFileBuilder::with transformer does not modify the class file + return ClassFile.of().transform(transformedModel, ClassFileBuilder::with); + } + + private static ClassModel readClassFile(File file) throws IOException { + return ClassFile.of().parse(Files.readAllBytes(file.toPath())); + } + + public static void writeClassfile(File file, byte[] bytes) throws IOException { + Files.write(file.toPath(), bytes); + } +} diff --git a/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java b/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java new file mode 100644 index 0000000000..08609bdb45 --- /dev/null +++ b/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java @@ -0,0 +1,122 @@ +package org.example.canonicalize; + +import java.lang.classfile.AccessFlags; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassElement; +import java.lang.classfile.ClassFile; +import java.lang.classfile.ClassFileVersion; +import java.lang.classfile.ClassModel; +import java.lang.classfile.ClassTransform; +import java.lang.classfile.CustomAttribute; +import java.lang.classfile.FieldModel; +import java.lang.classfile.Interfaces; +import java.lang.classfile.MethodModel; +import java.lang.classfile.Superclass; +import java.lang.classfile.attribute.CompilationIDAttribute; +import java.lang.classfile.attribute.DeprecatedAttribute; +import java.lang.classfile.attribute.EnclosingMethodAttribute; +import java.lang.classfile.attribute.InnerClassesAttribute; +import java.lang.classfile.attribute.ModuleAttribute; +import java.lang.classfile.attribute.ModuleHashesAttribute; +import java.lang.classfile.attribute.ModuleMainClassAttribute; +import java.lang.classfile.attribute.ModulePackagesAttribute; +import java.lang.classfile.attribute.ModuleResolutionAttribute; +import java.lang.classfile.attribute.ModuleTargetAttribute; +import java.lang.classfile.attribute.NestHostAttribute; +import java.lang.classfile.attribute.NestMembersAttribute; +import java.lang.classfile.attribute.PermittedSubclassesAttribute; +import java.lang.classfile.attribute.RecordAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute; +import java.lang.classfile.attribute.SignatureAttribute; +import java.lang.classfile.attribute.SourceDebugExtensionAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.classfile.attribute.SourceIDAttribute; +import java.lang.classfile.attribute.SyntheticAttribute; +import java.lang.classfile.attribute.UnknownAttribute; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ConstantPoolTransform { + + private static List correctOrder = new ArrayList<>(); + private static Map oldIndexToNewIndex = new HashMap<>(); + + public static ClassModel transform(ClassModel classModel) { + // this_class gets index 0 (1 in constant pool) + correctOrder.add(classModel.thisClass().index()); + establishCorrectOrder(classModel); + + return classModel; + } + + private static void establishCorrectOrder(ClassModel classModel) { + ClassFile.of().transform(classModel, new ClassTransform() { + + @Override + public void accept(ClassBuilder builder, ClassElement element) { + switch (element) { + case AccessFlags accessFlags -> { + // Access flags are not in the constant pool + } + case ClassFileVersion classFileVersion -> { + // Class file version is not in the constant pool + } + case CustomAttribute customAttribute -> {} + case FieldModel fieldModel -> { + correctOrder.add(fieldModel.fieldName().index()); + correctOrder.add(fieldModel.fieldType().index()); + } + case Interfaces interfaces -> { + correctOrder.addAll(interfaces.interfaces().stream() + .map(i -> i.index()) + .collect(Collectors.toUnmodifiableList())); + } + case MethodModel methodModel -> { + correctOrder.add(methodModel.methodName().index()); + correctOrder.add(methodModel.methodType().index()); + + // methodModel.code().ifPresent(code -> { + // correctOrder.add(code.elementList()); + // }); + + } + case Superclass superclass -> { + correctOrder.add(superclass.superclassEntry().index()); + } + case CompilationIDAttribute compilationIDAttribute -> {} + case DeprecatedAttribute deprecatedAttribute -> {} + case EnclosingMethodAttribute enclosingMethodAttribute -> {} + case InnerClassesAttribute innerClassesAttribute -> {} + case ModuleAttribute moduleAttribute -> {} + case ModuleHashesAttribute moduleHashesAttribute -> {} + case ModuleMainClassAttribute moduleMainClassAttribute -> {} + case ModulePackagesAttribute modulePackagesAttribute -> {} + case ModuleResolutionAttribute moduleResolutionAttribute -> {} + case ModuleTargetAttribute moduleTargetAttribute -> {} + case NestHostAttribute nestHostAttribute -> {} + case NestMembersAttribute nestMembersAttribute -> {} + case PermittedSubclassesAttribute permittedSubclassesAttribute -> {} + case RecordAttribute recordAttribute -> {} + case RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute -> {} + case RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute -> {} + case RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute -> {} + case RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute -> {} + case SignatureAttribute signatureAttribute -> {} + case SourceDebugExtensionAttribute sourceDebugExtensionAttribute -> {} + case SourceFileAttribute sourceFileAttribute -> { + correctOrder.add(sourceFileAttribute.sourceFile().index()); + } + case SourceIDAttribute sourceIDAttribute -> {} + case SyntheticAttribute syntheticAttribute -> {} + case UnknownAttribute unknownAttribute -> {} + } + } + }); + } +} diff --git a/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java b/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java new file mode 100644 index 0000000000..1279c40ee6 --- /dev/null +++ b/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java @@ -0,0 +1,19 @@ +package org.example.canonicalize; + +import static org.example.canonicalize.BytecodeTransformerCore.writeClassfile; + +import java.io.IOException; +import java.nio.file.Path; +import org.junit.jupiter.api.Test; + +public class ConstantPoolTransformerTest { + + private static final Path TEST_RESOURCE = Path.of("src/test/resources"); + + @Test + void test() throws IOException { + BytecodeTransformerCore transformer = + new BytecodeTransformerCore(TEST_RESOURCE.resolve("First.class").toFile()); + writeClassfile(TEST_RESOURCE.resolve("First-transformed.class").toFile(), transformer.getTransformedBytes()); + } +} diff --git a/canonicalize/src/test/resources/First.class b/canonicalize/src/test/resources/First.class new file mode 100644 index 0000000000..a2fe7747eb Binary files /dev/null and b/canonicalize/src/test/resources/First.class differ diff --git a/pom.xml b/pom.xml index 0a479ad47c..446202540d 100644 --- a/pom.xml +++ b/pom.xml @@ -25,22 +25,29 @@ commons-csv 1.11.0 - - - org.junit.jupiter - junit-jupiter-api - 5.10.3 - test - - org.assertj - assertj-core - 3.26.3 - test + info.picocli + picocli + 4.7.6 + + + org.junit.jupiter + junit-jupiter-api + 5.10.3 + test + + + org.assertj + assertj-core + 3.26.3 + test + + +