Skip to content

Commit

Permalink
feat: canonicalize based on ClassModel
Browse files Browse the repository at this point in the history
  • Loading branch information
algomaster99 committed Sep 10, 2024
1 parent 330bc1e commit 25eab72
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 19 deletions.
8 changes: 0 additions & 8 deletions ast/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,6 @@
<groupId>com.github.gumtreediff</groupId>
<artifactId>core</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
</dependency>
</dependencies>

<build>
Expand Down
43 changes: 43 additions & 0 deletions canonicalize/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,47 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>22</source>
<target>22</target>
<compilerArgs>--enable-preview</compilerArgs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<id>canonicalize</id>
<goals>
<goal>shade</goal>
</goals>
<phase>package</phase>
<configuration>
<finalName>canonicalize</finalName>
<createDependencyReducedPom>false</createDependencyReducedPom>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>org.example.canonicalize.BytecodeTransformer</Main-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<Integer> {

@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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Integer> correctOrder = new ArrayList<>();
private static Map<Integer, Integer> 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 -> {}
}
}
});
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Binary file added canonicalize/src/test/resources/First.class
Binary file not shown.
29 changes: 18 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,29 @@
<artifactId>commons-csv</artifactId>
<version>1.11.0</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
<groupId>info.picocli</groupId>
<artifactId>picocli</artifactId>
<version>4.7.6</version>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.10.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
Expand Down

0 comments on commit 25eab72

Please sign in to comment.