├── .editorconfig
├── .github
└── workflows
│ ├── build.yml
│ └── publish-snapshot.yml
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── buildSrc
├── build.gradle
└── src
│ └── main
│ └── java
│ └── GenerateParserTask.java
├── chasm
├── .settings
│ ├── org.eclipse.jdt.core.prefs
│ └── org.eclipse.jdt.ui.prefs
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── quiltmc
│ │ └── chasm
│ │ ├── api
│ │ ├── ChasmProcessor.java
│ │ ├── ClassResult.java
│ │ ├── Lock.java
│ │ ├── Transformation.java
│ │ ├── Transformer.java
│ │ ├── target
│ │ │ ├── NodeTarget.java
│ │ │ ├── SliceTarget.java
│ │ │ └── Target.java
│ │ └── util
│ │ │ ├── ClassInfo.java
│ │ │ └── Context.java
│ │ └── internal
│ │ ├── ChasmContext.java
│ │ ├── ClassData.java
│ │ ├── TransformationApplier.java
│ │ ├── TransformationSorter.java
│ │ ├── TransformerSorter.java
│ │ ├── asm
│ │ ├── ChasmClassWriter.java
│ │ ├── package-info.java
│ │ └── visitor
│ │ │ ├── ChasmAnnotationVisitor.java
│ │ │ ├── ChasmClassVisitor.java
│ │ │ ├── ChasmFieldVisitor.java
│ │ │ ├── ChasmMethodVisitor.java
│ │ │ ├── ChasmModuleVisitor.java
│ │ │ ├── ChasmRecordComponentVisitor.java
│ │ │ ├── LocalInterpreter.java
│ │ │ ├── LocalValue.java
│ │ │ └── package-info.java
│ │ ├── intrinsic
│ │ ├── ChasmIntrinsics.java
│ │ ├── FileBytesIntrinsic.java
│ │ ├── FileContentIntrinsic.java
│ │ └── IncludeIntrinsic.java
│ │ ├── metadata
│ │ ├── OriginMetadata.java
│ │ ├── PathMetadata.java
│ │ └── package-info.java
│ │ ├── package-info.java
│ │ ├── transformer
│ │ ├── ChasmLangTransformation.java
│ │ ├── ChasmLangTransformer.java
│ │ └── package-info.java
│ │ ├── tree
│ │ ├── ClassNode.java
│ │ ├── LazyMap.java
│ │ ├── package-info.java
│ │ └── reader
│ │ │ ├── AnnotationNodeReader.java
│ │ │ ├── ClassNodeReader.java
│ │ │ ├── FieldNodeReader.java
│ │ │ ├── MethodNodeReader.java
│ │ │ ├── ModuleNodeReader.java
│ │ │ ├── RecordComponentNodeReader.java
│ │ │ └── package-info.java
│ │ └── util
│ │ ├── ChasmEnvironment.java
│ │ ├── NodeConstants.java
│ │ ├── NodeUtils.java
│ │ ├── PathInitializer.java
│ │ └── package-info.java
│ ├── test
│ └── java
│ │ └── org
│ │ └── quiltmc
│ │ └── chasm
│ │ ├── IntrinsicsTest.java
│ │ ├── TestsBase.java
│ │ ├── TransformedTests.java
│ │ ├── TransformerSorterTests.java
│ │ └── UnchangedTests.java
│ └── testData
│ ├── java
│ ├── empty
│ │ ├── EmptyAnnotation.java
│ │ ├── EmptyClass.java
│ │ ├── EmptyEnum.java
│ │ ├── EmptyInterface.java
│ │ ├── EmptyOuterClass.java
│ │ ├── EmptyRecord.java
│ │ ├── EmptySealedClass.java
│ │ └── EmptySealedExtendsClass.java
│ └── other
│ │ ├── ExampleAnnotation.java
│ │ ├── ExampleClass.java
│ │ ├── ExampleEnum.java
│ │ ├── SimpleAnnotation.java
│ │ ├── TestLocalVariables.java
│ │ └── TestMergeInsns.java
│ ├── results
│ ├── .gitattributes
│ ├── add
│ │ ├── field_to_empty.result
│ │ ├── fields_to_empty.result
│ │ └── method_to_empty.result
│ ├── other
│ │ ├── test_local_variables.result
│ │ └── test_merge_insns.result
│ └── unchanged
│ │ ├── EmptyAnnotation.result
│ │ ├── EmptyClass.result
│ │ ├── EmptyEnum.result
│ │ ├── EmptyInterface.result
│ │ ├── EmptyOuterClass$EmptyInnerClass.result
│ │ ├── EmptyOuterClass$EmptyStaticNestedClass.result
│ │ ├── EmptyOuterClass.result
│ │ ├── EmptyRecord.result
│ │ ├── EmptySealedClass.result
│ │ ├── EmptySealedExtendsClass.result
│ │ ├── ExampleAnnotation.result
│ │ ├── ExampleClass$ExampleRecord.result
│ │ ├── ExampleClass.result
│ │ └── ExampleEnum.result
│ └── transformers
│ ├── add_field.chasm
│ ├── add_field_2.chasm
│ ├── add_method.chasm
│ ├── test_local_variables.chasm
│ ├── test_merge_insns.chasm
│ └── touch.chasm
├── chassembly
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── org
│ │ └── quiltmc
│ │ └── chasm
│ │ └── lang
│ │ ├── api
│ │ ├── ast
│ │ │ ├── Ast.java
│ │ │ ├── BinaryNode.java
│ │ │ ├── BooleanNode.java
│ │ │ ├── CallNode.java
│ │ │ ├── FloatNode.java
│ │ │ ├── IndexNode.java
│ │ │ ├── IntegerNode.java
│ │ │ ├── LambdaNode.java
│ │ │ ├── ListNode.java
│ │ │ ├── MapNode.java
│ │ │ ├── MemberNode.java
│ │ │ ├── Node.java
│ │ │ ├── NullNode.java
│ │ │ ├── ReferenceNode.java
│ │ │ ├── StringNode.java
│ │ │ ├── TernaryNode.java
│ │ │ ├── UnaryNode.java
│ │ │ └── ValueNode.java
│ │ ├── eval
│ │ │ ├── ClosureNode.java
│ │ │ ├── Evaluator.java
│ │ │ ├── FunctionNode.java
│ │ │ ├── IntrinsicFunction.java
│ │ │ └── Resolver.java
│ │ ├── exception
│ │ │ ├── EvaluationException.java
│ │ │ └── ParseException.java
│ │ └── metadata
│ │ │ └── Metadata.java
│ │ └── internal
│ │ ├── Assert.java
│ │ ├── eval
│ │ ├── EvaluatorImpl.java
│ │ ├── LambdaReference.java
│ │ ├── NodeReference.java
│ │ ├── Reference.java
│ │ ├── ResolverImpl.java
│ │ └── package-info.java
│ │ ├── intrinsics
│ │ ├── BuiltInIntrinsics.java
│ │ ├── CharsFunction.java
│ │ ├── EntriesFunction.java
│ │ ├── FlattenFunction.java
│ │ ├── FromEntriesFunction.java
│ │ ├── JoinFunction.java
│ │ ├── LenFunction.java
│ │ ├── MapFunction.java
│ │ ├── ReduceFunction.java
│ │ ├── SplitFloatFunction.java
│ │ ├── ToFloatFunction.java
│ │ ├── ToIntegerFunction.java
│ │ └── package-info.java
│ │ ├── package-info.java
│ │ ├── parse
│ │ ├── Parser.jj
│ │ ├── SourceSpan.java
│ │ └── package-info.java
│ │ └── render
│ │ ├── RenderUtil.java
│ │ ├── Renderer.java
│ │ └── package-info.java
│ └── test
│ ├── java
│ └── org
│ │ └── quiltmc
│ │ └── chasm
│ │ └── lang
│ │ ├── TestBase.java
│ │ ├── TestEvaluation.java
│ │ └── TestRendering.java
│ └── resources
│ ├── results
│ ├── .gitattributes
│ ├── complex
│ │ ├── basic.chasm
│ │ └── brainfuck.chasm
│ ├── intrinsics
│ │ ├── chars_join.chasm
│ │ ├── conversion.chasm
│ │ ├── entries.chasm
│ │ ├── flatten.chasm
│ │ ├── len.chasm
│ │ ├── map.chasm
│ │ ├── reduce.chasm
│ │ └── split_float.chasm
│ ├── lambdas
│ │ ├── capturing
│ │ │ ├── basic.chasm
│ │ │ └── global.chasm
│ │ ├── currying
│ │ │ └── basic.chasm
│ │ └── recursion
│ │ │ ├── basic.chasm
│ │ │ ├── indirect.chasm
│ │ │ └── map_arg.chasm
│ ├── literals
│ │ ├── basic.chasm
│ │ └── float.chasm
│ ├── references
│ │ └── global.chasm
│ ├── syntax
│ │ ├── comments.chasm
│ │ └── trailing_commas.chasm
│ └── ternary
│ │ └── nested.chasm
│ └── tests
│ ├── complex
│ ├── basic.chasm
│ └── brainfuck.chasm
│ ├── intrinsics
│ ├── chars_join.chasm
│ ├── conversion.chasm
│ ├── entries.chasm
│ ├── flatten.chasm
│ ├── len.chasm
│ ├── map.chasm
│ ├── reduce.chasm
│ └── split_float.chasm
│ ├── lambdas
│ ├── capturing
│ │ ├── basic.chasm
│ │ └── global.chasm
│ ├── currying
│ │ └── basic.chasm
│ └── recursion
│ │ ├── basic.chasm
│ │ ├── indirect.chasm
│ │ └── map_arg.chasm
│ ├── literals
│ ├── basic.chasm
│ └── float.chasm
│ ├── references
│ └── global.chasm
│ ├── syntax
│ ├── comments.chasm
│ └── trailing_commas.chasm
│ └── ternary
│ └── nested.chasm
├── config
└── checkstyle
│ └── checkstyle.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Test and Build
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout repository
10 | uses: actions/checkout@v2
11 | - name: Set up JDK 17
12 | uses: actions/setup-java@v2
13 | with:
14 | java-version: '17'
15 | distribution: 'temurin'
16 | cache: gradle
17 | - name: Grant execute permission for gradlew
18 | run: chmod +x gradlew
19 | - name: Build with Gradle
20 | run: ./gradlew build
21 |
--------------------------------------------------------------------------------
/.github/workflows/publish-snapshot.yml:
--------------------------------------------------------------------------------
1 | name: Publish Snapshot
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | publish:
10 | name: Publish Snapshot
11 | runs-on: ubuntu-latest
12 | container:
13 | image: openjdk:17-jdk
14 | options: --user root
15 | steps:
16 | - name: Checkout repository
17 | uses: actions/checkout@v2
18 | - name: Grant execute permission for gradlew
19 | run: chmod +x gradlew
20 | - name: Publish snapshot
21 | run: ./gradlew publishSnapshotPublicationToQuiltSnapshotRepository --stacktrace
22 | env:
23 | SNAPSHOTS_URL: ${{ secrets.SNAPSHOTS_URL }}
24 | SNAPSHOTS_USERNAME: ${{ secrets.SNAPSHOTS_USERNAME }}
25 | SNAPSHOTS_PASSWORD: ${{ secrets.SNAPSHOTS_PASSWORD }}
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle/
2 | .idea/
3 | .settings/
4 | .classpath
5 | .project
6 | build/
7 | bin/
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > NOTE: This project is still in its early development.
2 | > There's guaranteed bugs and missing functionality.
3 |
4 | # Chasm - Collision Handling ASM
5 |
6 | ## What is Chasm?
7 | Chasm is a Java bytecode transformation tool.
8 | In its base functionality, it's similar to [ASM](https://asm.ow2.io/).
9 | However, ASM is intended for single programs transforming some bytecode,
10 | whereas the goal of Chasm is to allow multiple users to transform the same code.
11 | This is useful in modding Java based games, where multiple mods might want to change
12 | the same code.
13 |
14 | ## Why use Chasm?
15 | There are other options for transforming bytecode.
16 | Two popular ones are [Mixins](https://github.com/SpongePowered/Mixin) and access-wideners.
17 | While they both perform well in their respective tasks, they are both limiting:
18 | Access wideners do one thing only and Mixin tries its best to be safe,
19 | preventing many useful transformations like changing control flow.
20 |
21 | Chasm aims to provide the full power of ASM without restrictions.
22 | This means that using Chasm directly might be tricky, but the goal of Chasm is to
23 | allow reimplementing Mixin and access wideners on top of Chasm.
24 |
25 | This greatly simplifies the toolchain, since only one bytecode transformation
26 | library needs to be applied.
27 | Additionally, if Mixin and AW are insufficient, people can implement
28 | another "frontend" for Chasm without requiring special toolchain support.
29 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | subprojects {
2 | apply plugin: 'checkstyle'
3 | apply plugin: 'java'
4 | apply plugin: 'maven-publish'
5 |
6 | group 'org.quiltmc.chasm'
7 | version '0.1.0'
8 |
9 | java {
10 | toolchain {
11 | languageVersion = JavaLanguageVersion.of(17)
12 | }
13 | }
14 |
15 | checkstyle {
16 | toolVersion "9.1"
17 | ignoreFailures(false)
18 | }
19 |
20 | publishing {
21 | repositories {
22 | maven {
23 | name = 'quiltSnapshot'
24 | url = System.getenv('SNAPSHOTS_URL')
25 |
26 | credentials {
27 | username = System.getenv('SNAPSHOTS_USERNAME')
28 | password = System.getenv('SNAPSHOTS_PASSWORD')
29 | }
30 | }
31 | }
32 |
33 | publications {
34 | snapshot(MavenPublication) {
35 | version = project.version + "-SNAPSHOT"
36 |
37 | from components.java
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/buildSrc/build.gradle:
--------------------------------------------------------------------------------
1 | repositories {
2 | exclusiveContent {
3 | forRepository {
4 | ivy {
5 | url 'https://javacc.com/download/'
6 |
7 | patternLayout {
8 | artifact '/[module].[ext]'
9 | }
10 |
11 | metadataSources { artifact() }
12 | }
13 | }
14 | filter {
15 | includeGroup "javacc21"
16 | }
17 | }
18 | }
19 |
20 |
21 | dependencies {
22 | implementation 'javacc21:javacc-full'
23 | }
24 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/GenerateParserTask.java:
--------------------------------------------------------------------------------
1 | import java.io.IOException;
2 | import java.util.Collections;
3 |
4 | import com.javacc.Main;
5 | import freemarker.template.TemplateException;
6 | import org.gradle.api.DefaultTask;
7 | import org.gradle.api.file.DirectoryProperty;
8 | import org.gradle.api.file.RegularFileProperty;
9 | import org.gradle.api.tasks.InputFile;
10 | import org.gradle.api.tasks.OutputDirectory;
11 | import org.gradle.api.tasks.TaskAction;
12 |
13 | public abstract class GenerateParserTask extends DefaultTask {
14 | @InputFile
15 | public abstract RegularFileProperty getGrammarFile();
16 |
17 | @OutputDirectory
18 | public abstract DirectoryProperty getOutputDir();
19 |
20 | @TaskAction
21 | public void run() {
22 | getProject().delete(getOutputDir());
23 |
24 | int returnValue;
25 | try {
26 | returnValue = Main.mainProgram(
27 | getGrammarFile().get().getAsFile().toPath(),
28 | getOutputDir().get().getAsFile().toPath(),
29 | "java",
30 | 8,
31 | true,
32 | Collections.emptyMap()
33 | );
34 | } catch (Exception e) {
35 | throw new RuntimeException(e);
36 | }
37 |
38 | if (returnValue != 0) {
39 | throw new RuntimeException("JavaCC returned a non-zero exit code.");
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/chasm/build.gradle:
--------------------------------------------------------------------------------
1 | compileJava {
2 | javaCompiler = javaToolchains.compilerFor {
3 | languageVersion = JavaLanguageVersion.of(8)
4 | }
5 | }
6 |
7 | repositories {
8 | mavenCentral()
9 | }
10 |
11 | dependencies {
12 | implementation project(":chassembly")
13 |
14 | implementation 'org.slf4j:slf4j-api:1.8.0-beta4'
15 | implementation 'org.ow2.asm:asm:9.2'
16 | implementation 'org.ow2.asm:asm-tree:9.2'
17 | implementation 'org.ow2.asm:asm-analysis:9.2'
18 |
19 | compileOnly 'org.jetbrains:annotations:23.0.0'
20 | testCompileOnly 'org.jetbrains:annotations:23.0.0'
21 |
22 | testImplementation 'org.junit.jupiter:junit-jupiter:5.8.1'
23 | testImplementation 'org.ow2.asm:asm-util:9.2'
24 | testImplementation 'com.googlecode.java-diff-utils:diffutils:1.3.0'
25 | testImplementation 'org.reflections:reflections:0.10.2'
26 | }
27 |
28 | test {
29 | useJUnitPlatform()
30 | }
31 |
32 | sourceSets {
33 | testData {
34 | java {
35 | compileClasspath = sourceSets.main.output + configurations.testCompileClasspath
36 | runtimeClasspath = sourceSets.main.output + project.configurations.testRuntimeClasspath
37 | }
38 | }
39 | }
40 |
41 | testClasses.dependsOn("testDataClasses")
42 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/ClassResult.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api;
2 |
3 | import org.jetbrains.annotations.NotNull;
4 | import org.jetbrains.annotations.Nullable;
5 | import org.quiltmc.chasm.lang.api.metadata.Metadata;
6 |
7 | /**
8 | * The result type used for {@link org.quiltmc.chasm.api.ChasmProcessor#process()}.
9 | */
10 | public class ClassResult {
11 | private final byte @Nullable [] classBytes;
12 |
13 | private final @NotNull Metadata metadata;
14 |
15 | private final @NotNull Type type;
16 |
17 | /**
18 | * Create a new {@link ClassResult}.
19 | *
20 | * @param classBytes The bytes representing the class of this result,
21 | * or {@code null} if {@code type = Type.Removed}.
22 | * @param metadata The metadata associated with the class of this result.
23 | * @param type The {@link Type} of this result.
24 | */
25 | public ClassResult(byte @Nullable [] classBytes, @NotNull Metadata metadata, @NotNull Type type) {
26 | this.classBytes = classBytes;
27 | this.metadata = metadata;
28 | this.type = type;
29 | }
30 |
31 | /**
32 | * Get the bytes representing the class of this result.
33 | * This will be {@code null} if and only if {@link #getType} is {@link Type#REMOVED}.
34 | *
35 | * @return The bytes representing the class of this result,
36 | * or {@code null} if {@link #getType} is {@link Type#REMOVED}.
37 | */
38 | public byte @Nullable [] getClassBytes() {
39 | return classBytes;
40 | }
41 |
42 | /**
43 | * Get the {@link Metadata} associated with the class of this result.
44 | *
45 | * @return The {@link Metadata} of the class.
46 | */
47 | public @NotNull Metadata getMetadata() {
48 | return metadata;
49 | }
50 |
51 | /**
52 | * Get the {@link Type} of this result.
53 | *
54 | * @return The {@link Type} of this result.
55 | */
56 | public @NotNull Type getType() {
57 | return type;
58 | }
59 |
60 | /**
61 | * Represents the possible outcomes of {@link ClassResult}.
62 | */
63 | public enum Type {
64 | /**
65 | * Indicates that the class hasn't been modified during processing.
66 | * Note that this doesn't apply to {@link org.quiltmc.chasm.lang.api.metadata.Metadata}.
67 | */
68 | UNMODIFIED,
69 |
70 | /**
71 | * Indicates that the class may have been modified during processing.
72 | */
73 | MODIFIED,
74 |
75 | /**
76 | * Indicates that this class was created during processing.
77 | */
78 | ADDED,
79 |
80 | /**
81 | * Indicates that this class was deleted during processing.
82 | * This means that {@link #getClassBytes} will return null.
83 | */
84 | REMOVED
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/Lock.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api;
2 |
3 | /**
4 | * Todo.
5 | */
6 | public enum Lock {
7 | NONE,
8 | BEFORE,
9 | AFTER
10 | }
11 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/Transformation.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api;
2 |
3 | import java.util.Collections;
4 | import java.util.Map;
5 |
6 | import org.quiltmc.chasm.api.target.Target;
7 | import org.quiltmc.chasm.lang.api.ast.Node;
8 |
9 | /**
10 | * A Transformation of a {@link #getTarget() target} using specified {@link #getSources() sources}.
11 | */
12 | public interface Transformation {
13 | /**
14 | * Gets this {@link Transformation Transformation's} parent {@link Transformer}.
15 | *
16 | * @return The parent {@code Transformer} of this {@code Transformation}.
17 | */
18 | Transformer getParent();
19 |
20 | /**
21 | * Gets this {@link Transformation Transformation's} {@link Target}.
22 | *
23 | * @return The {@code Target} of this {@code Transformation}.
24 | */
25 | Target getTarget();
26 |
27 | /**
28 | * Gets this {@link Transformation Transformation's} map of named source {@link Target Targets}.
29 | *
30 | *
Sources are the {@link Node Nodes} that this {@code Transformation} requests as input.
31 | *
32 | * @return This {@code Transformation Transformation's} input sources
33 | * as a map of string-named {@code Target targets}.
34 | */
35 | default Map getSources() {
36 | return Collections.emptyMap();
37 | }
38 |
39 | /**
40 | * Applies this {@link Transformation} to the given {@link Node},
41 | * possibly using its {@code Node} sources.
42 | *
43 | * @param targetNode A {@code Node} to apply this {@code Transformation} to.
44 | *
45 | * @param nodeSources The sources of the target {@code Node}.
46 | *
47 | * @return The {@code Node} resulting from applying this {@code Transformation}.
48 | */
49 | Node apply(Node targetNode, Map nodeSources);
50 | }
51 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/Transformer.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api;
2 |
3 | import java.util.Collection;
4 | import java.util.Collections;
5 | import java.util.Set;
6 |
7 | import org.quiltmc.chasm.lang.api.ast.ListNode;
8 |
9 | /**
10 | * Bulk-instantiates {@link Transformation}s of classes.
11 | *
12 | * {@link Transformer}s also provide a {@code String} ID for use in occasionally
13 | * hard-coding relative {@code Transformer} ordering.
14 | */
15 | public interface Transformer {
16 | /**
17 | * Applies this {@link Transformer} to the given {@link ListNode} of classes,
18 | * resulting in a {@code Collection} of {@link Transformation}s.
19 | *
20 | * @param classes The {@code ListNode} of classes available to transform.
21 | *
22 | * @return A {@code Collection} of {@code Transformation}s this
23 | * {@code Transformer} created from the given {@code ListNode} of classes.
24 | */
25 | Collection apply(ListNode classes);
26 |
27 | /**
28 | * Gets the ID of this {@link Transformer}.
29 | *
30 | * The ID string must be a string unique among all other transformers.
31 | * E.g. org.example.transformers.ExampleTransformer
32 | *
33 | * @return The unique ID of this Transformer.
34 | */
35 | String getId();
36 |
37 | /**
38 | * Defines explicit dependencies between the {@link Transformation}s of {@link Transformer}s.
39 | * All Transformations defined by this Transformer *must run after* all Transformations
40 | * defined by all the Transformers whose ID is in the returned {@link Set}.
41 | *
42 | * @param transformerIds All known transformer IDs.
43 | * @return A set of Transformer IDs this Transformer must run after.
44 | *
45 | * @see #getId()
46 | */
47 | default Set mustRunAfter(Set transformerIds) {
48 | return Collections.emptySet();
49 | }
50 |
51 | /**
52 | * Defines explicit dependencies between the {@link Transformation}s of {@link Transformer}s.
53 | * All Transformations defined by this Transformer *must run before* all Transformations
54 | * defined by all the Transformers whose ID is in the returned {@link Set}.
55 | *
56 | * @param transformerIds All known transformer IDs.
57 | * @return A Set of Transformer IDs this Transformer must run before.
58 | *
59 | * @see #getId()
60 | */
61 | default Set mustRunBefore(Set transformerIds) {
62 | return Collections.emptySet();
63 | }
64 |
65 |
66 | /**
67 | * Defines explicit dependencies between {@link Transformer}s.
68 | * This Transformer *must run after* all the Transformers whose ID is in the returned {@link Set}.
69 | *
70 | * @param transformerIds All known transformer IDs.
71 | * @return A {@link Set} of Transformer IDs this Transformer must run after.
72 | *
73 | * @see #getId()
74 | */
75 | default Set mustRunRoundAfter(Set transformerIds) {
76 | return Collections.emptySet();
77 | }
78 |
79 | /**
80 | * Defines explicit dependencies between {@link Transformer}s.
81 | * This Transformer *must run before* all the Transformers whose ID is in the returned {@link Set}.
82 | *
83 | * @param transformerIds All known transformer IDs.
84 | * @return A {@link Set} of Transformer IDs this Transformer must run before.
85 | *
86 | * @see #getId()
87 | */
88 | default Set mustRunRoundBefore(Set transformerIds) {
89 | return Collections.emptySet();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/target/NodeTarget.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api.target;
2 |
3 | import org.quiltmc.chasm.api.Lock;
4 | import org.quiltmc.chasm.lang.api.ast.Node;
5 |
6 | /**
7 | * Locates a {@link Node}, as a {@link Target}.
8 | */
9 | public class NodeTarget extends Target {
10 | /**
11 | * Makes a new {@link NodeTarget} that targets the passed {@link Node}.
12 | *
13 | * @param node The {@link Node} to target.
14 | */
15 | public NodeTarget(Node node) {
16 | super(node, Lock.NONE);
17 | }
18 |
19 | /**
20 | * Makes a new {@link NodeTarget} that targets the passed {@link Node} and applies the specified {@link Lock}.
21 | *
22 | * @param node The {@link Node} to target.
23 | * @param lock The {@link Lock} to apply.
24 | */
25 | public NodeTarget(Node node, Lock lock) {
26 | super(node, lock);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/target/SliceTarget.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api.target;
2 |
3 | import java.util.List;
4 |
5 | import org.quiltmc.chasm.api.Lock;
6 | import org.quiltmc.chasm.lang.api.ast.ListNode;
7 |
8 | /**
9 | * Slices a {@link ListNode}, as a {@link Target}.
10 | *
11 | * A slice of a list is a contiguous subset of the list, like
12 | * {@link List#subList}.
13 | */
14 | public class SliceTarget extends Target {
15 | // NOTE! "Virtual Index". Divide by two for actual list index
16 | private int startIndex;
17 | private int endIndex;
18 |
19 | /**
20 | * Creates a {@link SliceTarget} of the passed {@link ListNode}.
21 | *
22 | * @param target The {@code ListNode} to slice.
23 | *
24 | * @param start The first index of the {@code ListNode} included in the
25 | * {@code SliceTarget}.
26 | *
27 | * @param end The first index after the {@code start} of the {@link ListNode}
28 | * not included in the {@code SliceTarget}.
29 | *
30 | */
31 | public SliceTarget(ListNode target, int start, int end) {
32 | super(target, Lock.NONE);
33 | startIndex = start;
34 | endIndex = end;
35 | }
36 |
37 | /**
38 | * Creates a {@link SliceTarget} of the passed {@link ListNode} and applies the specified {@link Lock}.
39 | *
40 | * @param target The {@code ListNode} to slice.
41 | *
42 | * @param start The first index of the {@code ListNode} included in the
43 | * {@code SliceTarget}.
44 | *
45 | * @param end The first index after the {@code start} of the {@link ListNode}
46 | * not included in the {@code SliceTarget}.
47 | *
48 | * @param lock The {@link Lock} to apply.
49 | */
50 | public SliceTarget(ListNode target, int start, int end, Lock lock) {
51 | super(target, lock);
52 | startIndex = start;
53 | endIndex = end;
54 | }
55 |
56 | /**
57 | * Gets this {@link SliceTarget}'s slice start index.
58 | *
59 | *
The slice start index is the first index of the {@link ListNode} included in this {@code SliceTarget}.
60 | *
61 | * @return The start index of this {@code SliceTarget}' slice.
62 | */
63 | public int getStartIndex() {
64 | return startIndex;
65 | }
66 |
67 | /**
68 | * Sets this {@link SliceTarget}'s slice start index.
69 | *
70 | *
The slice start index is the first index of the {@link ListNode} included in this {@code SliceTarget}.
71 | *
72 | * @param startIndex The new start index for this {@code SliceTarget}'s slice.
73 | */
74 | public void setStartIndex(int startIndex) {
75 | this.startIndex = startIndex;
76 | }
77 |
78 | /**
79 | * Gets this {@link SliceTarget}'s slice end index.
80 | *
81 | *
The slice end index is the first index after the {@code start} of the {@link ListNode}
82 | * not included in this {@code SliceTarget}.
83 | *
84 | * @return The end index of this {@code SliceTarget}'s slice.
85 | */
86 | public int getEndIndex() {
87 | return endIndex;
88 | }
89 |
90 | /**
91 | * Set this {@link SliceTarget}'s slice end index.
92 | *
93 | *
The slice end index is the first index after the {@code start} of the {@link ListNode}
94 | * not included in this {@code SliceTarget}.
95 | *
96 | * @param endIndex The new end index for this {@code SliceTarget}'s slice.
97 | */
98 | public void setEndIndex(int endIndex) {
99 | this.endIndex = endIndex;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/target/Target.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api.target;
2 |
3 | import org.quiltmc.chasm.api.Lock;
4 | import org.quiltmc.chasm.lang.api.ast.Node;
5 |
6 | /**
7 | * Defines a range of {@link Node}s.
8 | */
9 | public abstract class Target {
10 | private final Node node;
11 | private final Lock lock;
12 |
13 | public Target(Node node, Lock lock) {
14 | this.node = node;
15 | this.lock = lock;
16 | }
17 |
18 | /**
19 | * Gets a {@link Node} containing the {@link Target}.
20 | *
21 | * @return The targeted {@link Node}.
22 | */
23 | public Node getTarget() {
24 | return node;
25 | }
26 |
27 | /**
28 | * Gets the {@link Lock} applied to the {@link Target}.
29 | *
30 | * @return The targeted {@link Node}[s] as a {@link Node}.
31 | */
32 | public Lock getLock() {
33 | return lock;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/util/ClassInfo.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api.util;
2 |
3 | import java.util.Arrays;
4 |
5 | import org.jetbrains.annotations.Contract;
6 | import org.objectweb.asm.ClassReader;
7 | import org.objectweb.asm.Opcodes;
8 |
9 | /**
10 | * Static information related to a specific class.
11 | */
12 | public class ClassInfo {
13 | public static final String OBJECT = "java/lang/Object";
14 |
15 | private final String className;
16 | private final String superClass;
17 | private final String[] interfaces;
18 | private final boolean isInterface;
19 |
20 | /**
21 | * Construct a new {@link ClassInfo}.
22 | *
23 | * @param className The name of the class (see {@link #getClassName()}).
24 | * @param superClass The name of the super class (see {@link #getSuperClass()}).
25 | * @param interfaces The names of the implemented interfaces (see {@link #getInterfaces()}).
26 | * @param isInterface Whether this class is an interface (see {@link #isInterface}).
27 | */
28 | public ClassInfo(String className, String superClass, String[] interfaces, boolean isInterface) {
29 | this.className = className;
30 | this.superClass = superClass;
31 | this.interfaces = interfaces;
32 | this.isInterface = isInterface;
33 | }
34 |
35 | /**
36 | * Returns the internal name of the class (See {@link org.objectweb.asm.Type#getInternalName}).
37 | *
38 | * @return The internal name of the class.
39 | */
40 | public String getClassName() {
41 | return className;
42 | }
43 |
44 | /**
45 | * Returns the internal name of the super class (See {@link org.objectweb.asm.Type#getInternalName}).
46 | * For interfaces, this should return {@link #OBJECT}.
47 | * Only for the {@link Object} class ({@link #OBJECT}) will this return null.
48 | *
49 | * @return The internal name of the super class.
50 | */
51 | public String getSuperClass() {
52 | return superClass;
53 | }
54 |
55 | /**
56 | * Returns the internal names of the implemented interfaces (See {@link org.objectweb.asm.Type#getInternalName}).
57 | *
58 | * @return The internal names of the interfaces.
59 | */
60 | public String[] getInterfaces() {
61 | return interfaces;
62 | }
63 |
64 | /**
65 | * Returns whether the class is an interface.
66 | *
67 | * @return Whether the class is an interface.
68 | */
69 | public boolean isInterface() {
70 | return isInterface;
71 | }
72 |
73 | /**
74 | * Extract class information from the provided {@link Class} object using reflection.
75 | *
76 | * @param clazz The class from which to extract information.
77 | * @return The information extracted from the provided class.
78 | */
79 | public static ClassInfo fromClass(Class> clazz) {
80 | if (clazz == Object.class) {
81 | return new ClassInfo(
82 | ClassInfo.OBJECT,
83 | null,
84 | new String[0],
85 | false
86 | );
87 | }
88 |
89 | return new ClassInfo(
90 | clazz.getName().replace('.', '/'),
91 | clazz.isInterface() ? ClassInfo.OBJECT : clazz.getSuperclass().getName().replace('.', '/'),
92 | Arrays.stream(clazz.getInterfaces()).map(c -> c.getName().replace('.', '/')).toArray(String[]::new),
93 | clazz.isInterface()
94 | );
95 | }
96 |
97 | /**
98 | * Extract class information from the provided binary representation of a class.
99 | * Such a binary representation can be obtained by reading a class file.
100 | *
101 | * @param classBytes The binary representation of a class, e.g. a class file.
102 | * @return The information extracted from the provided class.
103 | */
104 | public static ClassInfo fromBytes(byte[] classBytes) {
105 | ClassReader reader = new ClassReader(classBytes);
106 | return new ClassInfo(
107 | reader.getClassName(),
108 | reader.getSuperName(),
109 | reader.getInterfaces(),
110 | (reader.getAccess() & Opcodes.ACC_INTERFACE) != 0
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/api/util/Context.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.api.util;
2 |
3 | import org.jetbrains.annotations.Contract;
4 | import org.jetbrains.annotations.Nullable;
5 |
6 | /**
7 | * The context needed for chasm to function:
8 | *
9 | * - information about classes (superclass, type, etc)
10 | * - cacheable file system access
11 | *
12 | *
13 | *
15 | * Binary names in JVMS 17.
16 | *
17 | *
All methods in this class must be thread-safe, as they may be called concurrently.
18 | */
19 | public interface Context {
20 | /**
21 | * Returns information about a class.
22 | *
23 | * @param className The internal name of a class to query (See {@link org.objectweb.asm.Type#getInternalName}).
24 | * @return The {@link ClassInfo} corresponding to the specified {@code className},
25 | * or {@code null} if it couldn't be located.
26 | */
27 | @Contract(pure = true)
28 | @Nullable ClassInfo getClassInfo(String className);
29 |
30 | /**
31 | * Returns the contents of the file at the given path, or {@code null} if the file does not exist or if chasm
32 | * processors shouldn't have access to this file.
33 | *
34 | *
Important: like the other functions in this interface, implementations of this function must
35 | * be pure, which means you should be mindful of the possibility of the file contents changing between invocations
36 | * of this method. A possible solution is to only read the file once and store it in memory.
37 | *
38 | *
It is possible to retain the cacheability of chasm while supporting this method, by remembering all files this
39 | * method is called with and their hashes, and storing them for the next time chasm is run (checking against the
40 | * hashes of the files then).
41 | *
42 | *
The format of the {@code path} is up to the implementer, for example different symbols might signify
43 | * different types of files (whether to look in a jar or in the file system). However, the accepted format of the
44 | * path should not depend on the operating system (e.g. \ must not be accepted on Windows if it is not going to be
45 | * accepted on Linux); this is to preserve purity across operating systems.
46 | *
47 | * @param path The implementation-dependent path to the file
48 | * @return The bytes in the file, or {@code null} if the file doesn't exist or is inaccessible
49 | */
50 | @Contract(pure = true)
51 | byte @Nullable [] readFile(String path);
52 | }
53 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/ChasmContext.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal;
2 |
3 | import java.util.Collections;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.Set;
7 | import java.util.stream.Collectors;
8 |
9 | import org.jetbrains.annotations.Nullable;
10 | import org.objectweb.asm.Opcodes;
11 | import org.quiltmc.chasm.api.util.ClassInfo;
12 | import org.quiltmc.chasm.api.util.Context;
13 | import org.quiltmc.chasm.internal.util.NodeConstants;
14 | import org.quiltmc.chasm.internal.util.NodeUtils;
15 | import org.quiltmc.chasm.lang.api.ast.ListNode;
16 | import org.quiltmc.chasm.lang.api.ast.MapNode;
17 | import org.quiltmc.chasm.lang.api.ast.Node;
18 |
19 | public class ChasmContext implements Context {
20 | private final Context parent;
21 | private final ListNode classes;
22 |
23 | public ChasmContext(Context parent, ListNode classes) {
24 | this.parent = parent;
25 | this.classes = classes;
26 | }
27 |
28 | @Override
29 | public ClassInfo getClassInfo(String className) {
30 | for (Node classNode : classes.getEntries()) {
31 | String name = NodeUtils.getAsString(classNode, NodeConstants.NAME);
32 | if (!name.equals(className)) {
33 | continue;
34 | }
35 |
36 | String superName = NodeUtils.getAsString(classNode, NodeConstants.SUPER);
37 | int access = NodeUtils.getAsInt(classNode, NodeConstants.ACCESS);
38 | ListNode interfaces = NodeUtils.getAsList(classNode, NodeConstants.INTERFACES);
39 |
40 | return new ClassInfo(
41 | className,
42 | superName == null && !className.equals(ClassInfo.OBJECT) ? ClassInfo.OBJECT : superName,
43 | interfaces.getEntries().stream().map(NodeUtils::asString).toArray(String[]::new),
44 | (access & Opcodes.ACC_INTERFACE) != 0
45 | );
46 | }
47 |
48 | return parent.getClassInfo(className);
49 | }
50 |
51 | @Override
52 | public byte @Nullable [] readFile(String path) {
53 | return parent.readFile(path);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/ClassData.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal;
2 |
3 | import org.jetbrains.annotations.ApiStatus;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.quiltmc.chasm.lang.api.metadata.Metadata;
6 |
7 | /**
8 | * Represents the data of a class used for transforming.
9 | */
10 | @ApiStatus.Internal
11 | public final class ClassData {
12 | private final byte @NotNull [] classBytes;
13 | private final @NotNull Metadata metadata;
14 |
15 | public ClassData(byte @NotNull [] classBytes, @NotNull Metadata metadata) {
16 | this.classBytes = classBytes;
17 | this.metadata = metadata;
18 | }
19 |
20 | public byte @NotNull [] getClassBytes() {
21 | return classBytes;
22 | }
23 |
24 | public @NotNull Metadata getMetadata() {
25 | return metadata;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/ChasmClassWriter.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm;
2 |
3 | import org.objectweb.asm.ClassWriter;
4 | import org.quiltmc.chasm.api.util.ClassInfo;
5 | import org.quiltmc.chasm.api.util.Context;
6 |
7 | public class ChasmClassWriter extends ClassWriter {
8 | private final Context context;
9 |
10 | public ChasmClassWriter(Context context) {
11 | super(COMPUTE_FRAMES);
12 |
13 | this.context = context;
14 | }
15 |
16 | @Override
17 | protected String getCommonSuperClass(String type1, String type2) {
18 | String current1 = type1;
19 | while (!current1.equals(ClassInfo.OBJECT)) {
20 | String current2 = type2;
21 | while (!current2.equals(ClassInfo.OBJECT)) {
22 | if (current1.equals(current2)) {
23 | return current1;
24 | }
25 | current2 = context.getClassInfo(current2).getSuperClass();
26 | }
27 | current1 = context.getClassInfo(current1).getSuperClass();
28 | }
29 |
30 | return ClassInfo.OBJECT;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/package-info.java:
--------------------------------------------------------------------------------
1 | @ApiStatus.Internal
2 | package org.quiltmc.chasm.internal.asm;
3 |
4 | import org.jetbrains.annotations.ApiStatus;
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmAnnotationVisitor.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm.visitor;
2 |
3 | import org.objectweb.asm.AnnotationVisitor;
4 | import org.quiltmc.chasm.internal.util.NodeConstants;
5 | import org.quiltmc.chasm.internal.util.NodeUtils;
6 | import org.quiltmc.chasm.lang.api.ast.Ast;
7 | import org.quiltmc.chasm.lang.api.ast.ListNode;
8 | import org.quiltmc.chasm.lang.api.ast.MapNode;
9 | import org.quiltmc.chasm.lang.api.ast.Node;
10 |
11 | public class ChasmAnnotationVisitor extends AnnotationVisitor {
12 | private final Node values;
13 |
14 | public ChasmAnnotationVisitor(int api, Node values) {
15 | this(api, values, null);
16 | }
17 |
18 | public ChasmAnnotationVisitor(int api, Node values, AnnotationVisitor av) {
19 | super(api, av);
20 |
21 | this.values = values;
22 | }
23 |
24 | @Override
25 | public void visit(String name, Object value) {
26 | if (value instanceof Object[]) {
27 | // Process Object[] using visitor pattern
28 | AnnotationVisitor visitor = visitArray(name);
29 | for (Object entry : (Object[]) value) {
30 | visitor.visit(null, entry);
31 | }
32 | visitor.visitEnd();
33 | return;
34 | }
35 |
36 | visitValueNode(name, NodeUtils.getValueNode(value));
37 |
38 | super.visit(name, value);
39 | }
40 |
41 |
42 | @Override
43 | public void visitEnum(String name, String descriptor, String value) {
44 | MapNode enumValueNode = Ast.map()
45 | .put(NodeConstants.DESCRIPTOR, descriptor)
46 | .put(NodeConstants.VALUE, value)
47 | .build();
48 |
49 | visitValueNode(name, enumValueNode);
50 |
51 | super.visitEnum(name, descriptor, value);
52 | }
53 |
54 | @Override
55 | public AnnotationVisitor visitAnnotation(String name, String descriptor) {
56 | MapNode values = Ast.emptyMap();
57 | MapNode annotationValueNode = Ast.map()
58 | .put(NodeConstants.DESCRIPTOR, descriptor)
59 | .put(NodeConstants.VALUES, values)
60 | .build();
61 |
62 | visitValueNode(name, annotationValueNode);
63 |
64 | return new ChasmAnnotationVisitor(api, values, super.visitAnnotation(name, descriptor));
65 | }
66 |
67 | @Override
68 | public AnnotationVisitor visitArray(String name) {
69 | ListNode values = Ast.emptyList();
70 |
71 | visitValueNode(name, values);
72 |
73 | return new ChasmAnnotationVisitor(api, values, super.visitArray(name));
74 | }
75 |
76 | @Override
77 | public void visitEnd() {
78 | // Nothing to do
79 | super.visitEnd();
80 | }
81 |
82 | private void visitValueNode(String name, Node value) {
83 | // If this is processing a map, name must be set
84 | if (values instanceof MapNode) {
85 | if (name == null) {
86 | throw new RuntimeException("Annotation value is missing name");
87 | }
88 |
89 | NodeUtils.asMap(values).put(name, value);
90 | }
91 |
92 | // If this is processing a list, name must not be set
93 | if (values instanceof ListNode) {
94 | NodeUtils.asList(values).add(value);
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmFieldVisitor.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm.visitor;
2 |
3 | import org.objectweb.asm.AnnotationVisitor;
4 | import org.objectweb.asm.Attribute;
5 | import org.objectweb.asm.FieldVisitor;
6 | import org.objectweb.asm.TypePath;
7 | import org.quiltmc.chasm.internal.util.NodeConstants;
8 | import org.quiltmc.chasm.lang.api.ast.Ast;
9 | import org.quiltmc.chasm.lang.api.ast.ListNode;
10 | import org.quiltmc.chasm.lang.api.ast.MapNode;
11 |
12 | public class ChasmFieldVisitor extends FieldVisitor {
13 |
14 | private final ListNode annotations = Ast.emptyList();
15 |
16 | public ChasmFieldVisitor(int api, MapNode fieldNode) {
17 | super(api);
18 |
19 | fieldNode.put(NodeConstants.ANNOTATIONS, annotations);
20 | }
21 |
22 | @Override
23 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
24 | MapNode values = Ast.emptyMap();
25 | MapNode annotation = Ast.map()
26 | .put(NodeConstants.DESCRIPTOR, descriptor)
27 | .put(NodeConstants.VISIBLE, visible)
28 | .put(NodeConstants.VALUES, values)
29 | .build();
30 | annotations.add(annotation);
31 |
32 | return new ChasmAnnotationVisitor(api, values);
33 | }
34 |
35 | @Override
36 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
37 | MapNode values = Ast.emptyMap();
38 | MapNode annotation = Ast.map()
39 | .put(NodeConstants.DESCRIPTOR, descriptor)
40 | .put(NodeConstants.VISIBLE, visible)
41 | .put(NodeConstants.VALUES, values)
42 | .put(NodeConstants.TYPE_REF, typeRef)
43 | .put(NodeConstants.TYPE_PATH, typePath.toString())
44 | .build();
45 | annotations.add(annotation);
46 |
47 | return new ChasmAnnotationVisitor(api, values);
48 | }
49 |
50 | @Override
51 | public void visitAttribute(Attribute attribute) {
52 | throw new RuntimeException("Unknown attribute: " + attribute.type);
53 | }
54 |
55 | @Override
56 | public void visitEnd() {
57 | // Nothing to do here
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmModuleVisitor.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm.visitor;
2 |
3 | import org.objectweb.asm.ModuleVisitor;
4 | import org.quiltmc.chasm.internal.util.NodeConstants;
5 | import org.quiltmc.chasm.lang.api.ast.Ast;
6 | import org.quiltmc.chasm.lang.api.ast.ListNode;
7 | import org.quiltmc.chasm.lang.api.ast.MapNode;
8 |
9 | public class ChasmModuleVisitor extends ModuleVisitor {
10 | private final MapNode moduleNode;
11 |
12 | private final ListNode packages = Ast.emptyList();
13 | private final ListNode requires = Ast.emptyList();
14 | private final ListNode exports = Ast.emptyList();
15 | private final ListNode opens = Ast.emptyList();
16 | private final ListNode uses = Ast.emptyList();
17 | private final ListNode provides = Ast.emptyList();
18 |
19 | public ChasmModuleVisitor(int api, MapNode moduleNode) {
20 | super(api);
21 | this.moduleNode = moduleNode;
22 |
23 | moduleNode.put(NodeConstants.PACKAGES, packages);
24 | moduleNode.put(NodeConstants.REQUIRES, requires);
25 | moduleNode.put(NodeConstants.EXPORTS, exports);
26 | moduleNode.put(NodeConstants.OPENS, opens);
27 | moduleNode.put(NodeConstants.USES, uses);
28 | moduleNode.put(NodeConstants.PROVIDERS, provides);
29 | }
30 |
31 | @Override
32 | public void visitMainClass(String mainClass) {
33 | moduleNode.put(NodeConstants.MAIN, Ast.literal(mainClass));
34 | }
35 |
36 | @Override
37 | public void visitPackage(String packaze) {
38 | packages.add(Ast.literal(packaze));
39 | }
40 |
41 | @Override
42 | public void visitRequire(String module, int access, String version) {
43 | MapNode requireNode = Ast.map()
44 | .put(NodeConstants.MODULE, module)
45 | .put(NodeConstants.ACCESS, access)
46 | .put(NodeConstants.VERSION, version)
47 | .build();
48 | requires.add(requireNode);
49 | }
50 |
51 | @Override
52 | public void visitExport(String packaze, int access, String... modules) {
53 | MapNode exportNode = Ast.map()
54 | .put(NodeConstants.PACKAGE, packaze)
55 | .put(NodeConstants.ACCESS, access)
56 | .build();
57 | if (modules != null) {
58 | exportNode.put(NodeConstants.MODULES, Ast.list((Object[]) modules));
59 | }
60 | exports.add(exportNode);
61 | }
62 |
63 | @Override
64 | public void visitOpen(String packaze, int access, String... modules) {
65 | MapNode openNode = Ast.map()
66 | .put(NodeConstants.PACKAGE, packaze)
67 | .put(NodeConstants.ACCESS, access)
68 | .build();
69 | if (modules != null) {
70 | openNode.put(NodeConstants.MODULES, Ast.list((Object[]) modules));
71 | }
72 | opens.add(openNode);
73 | }
74 |
75 | @Override
76 | public void visitUse(String service) {
77 | uses.add(Ast.literal(service));
78 | }
79 |
80 | @Override
81 | public void visitProvide(String service, String... providers) {
82 | MapNode provideNode = Ast.map()
83 | .put(NodeConstants.SERVICE, service)
84 | .put(NodeConstants.PROVIDERS, Ast.list((Object[]) providers))
85 | .build();
86 | provides.add(provideNode);
87 | }
88 |
89 | @Override
90 | public void visitEnd() {
91 | // Nothing to do here
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/ChasmRecordComponentVisitor.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm.visitor;
2 |
3 | import org.objectweb.asm.AnnotationVisitor;
4 | import org.objectweb.asm.Attribute;
5 | import org.objectweb.asm.RecordComponentVisitor;
6 | import org.objectweb.asm.TypePath;
7 | import org.quiltmc.chasm.internal.util.NodeConstants;
8 | import org.quiltmc.chasm.lang.api.ast.Ast;
9 | import org.quiltmc.chasm.lang.api.ast.ListNode;
10 | import org.quiltmc.chasm.lang.api.ast.MapNode;
11 |
12 | public class ChasmRecordComponentVisitor extends RecordComponentVisitor {
13 | private final ListNode annotations = Ast.emptyList();
14 |
15 | public ChasmRecordComponentVisitor(int api, MapNode recordComponentNode) {
16 | super(api);
17 |
18 | recordComponentNode.put(NodeConstants.ANNOTATIONS, annotations);
19 | }
20 |
21 | @Override
22 | public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
23 | MapNode values = Ast.emptyMap();
24 | MapNode annotation = Ast.map()
25 | .put(NodeConstants.DESCRIPTOR, descriptor)
26 | .put(NodeConstants.VISIBLE, visible)
27 | .put(NodeConstants.VALUES, values)
28 | .build();
29 | annotations.add(annotation);
30 |
31 | return new ChasmAnnotationVisitor(api, values);
32 | }
33 |
34 | @Override
35 | public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String descriptor, boolean visible) {
36 | MapNode values = Ast.emptyMap();
37 | MapNode annotation = Ast.map()
38 | .put(NodeConstants.DESCRIPTOR, descriptor)
39 | .put(NodeConstants.VISIBLE, visible)
40 | .put(NodeConstants.VALUES, values)
41 | .put(NodeConstants.TYPE_REF, typeRef)
42 | .put(NodeConstants.TYPE_PATH, typePath.toString())
43 | .build();
44 | annotations.add(annotation);
45 |
46 | return new ChasmAnnotationVisitor(api, values);
47 | }
48 |
49 | @Override
50 | public void visitAttribute(Attribute attribute) {
51 | throw new RuntimeException("Unknown attribute: " + attribute.type);
52 | }
53 |
54 | @Override
55 | public void visitEnd() {
56 | // Nothing to do here
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/LocalValue.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.asm.visitor;
2 |
3 | import java.util.Arrays;
4 | import java.util.Objects;
5 |
6 | import org.jetbrains.annotations.Nullable;
7 | import org.objectweb.asm.Type;
8 | import org.objectweb.asm.tree.AbstractInsnNode;
9 | import org.objectweb.asm.tree.InsnList;
10 | import org.objectweb.asm.tree.analysis.Value;
11 |
12 | public final class LocalValue implements Value {
13 | private final @Nullable Type type;
14 | private final int @Nullable[] sourceStores;
15 |
16 | public LocalValue(@Nullable Type type) {
17 | this.type = type;
18 | this.sourceStores = null;
19 | }
20 |
21 | public LocalValue(@Nullable Type type, InsnList instructions, AbstractInsnNode sourceStore) {
22 | this.type = type;
23 | this.sourceStores = new int[]{instructions.indexOf(sourceStore)};
24 | }
25 |
26 | public LocalValue(@Nullable Type type, int @Nullable[] sourceStores) {
27 | this.type = type;
28 | this.sourceStores = sourceStores;
29 | }
30 |
31 | public int @Nullable[] getSourceStores() {
32 | return sourceStores;
33 | }
34 |
35 | @Nullable
36 | public Type getType() {
37 | return type;
38 | }
39 |
40 | @Override
41 | public int getSize() {
42 | return type == null ? 1 : type.getSize();
43 | }
44 |
45 | @Override
46 | public int hashCode() {
47 | return (31 * Arrays.hashCode(sourceStores)) + Objects.hashCode(type);
48 | }
49 |
50 | @Override
51 | public boolean equals(Object other) {
52 | if (other == this) {
53 | return true;
54 | }
55 | if (!(other instanceof LocalValue)) {
56 | return false;
57 | }
58 | LocalValue that = (LocalValue) other;
59 | return Arrays.equals(this.sourceStores, that.sourceStores) && Objects.equals(this.type, that.type);
60 | }
61 |
62 | @Override
63 | public String toString() {
64 | return Arrays.toString(sourceStores) + " -> " + type;
65 | }
66 |
67 | public static int @Nullable[] mergeSourceStores(int @Nullable[] a, int @Nullable[] b) {
68 | if (a == null) {
69 | return b;
70 | } else if (b == null) {
71 | return a;
72 | } else {
73 | int[] ret = new int[a.length + b.length];
74 | int indexA = 0;
75 | int indexB = 0;
76 | int retIndex = 0;
77 | while (indexA < a.length || indexB < b.length) {
78 | if (indexA < a.length && (indexB >= b.length || a[indexA] <= b[indexB])) {
79 | if (indexB < b.length && a[indexA] == b[indexB]) {
80 | // prevent duplicates
81 | indexB++;
82 | }
83 | ret[retIndex++] = a[indexA++];
84 | } else {
85 | ret[retIndex++] = b[indexB++];
86 | }
87 | }
88 | if (retIndex < ret.length) {
89 | return Arrays.copyOf(ret, retIndex);
90 | }
91 | return ret;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/asm/visitor/package-info.java:
--------------------------------------------------------------------------------
1 | @ApiStatus.Internal
2 | package org.quiltmc.chasm.internal.asm.visitor;
3 |
4 | import org.jetbrains.annotations.ApiStatus;
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/ChasmIntrinsics.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.intrinsic;
2 |
3 | import org.quiltmc.chasm.api.util.Context;
4 | import org.quiltmc.chasm.lang.api.ast.Node;
5 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
6 |
7 | public final class ChasmIntrinsics {
8 | private ChasmIntrinsics() {
9 | }
10 |
11 | public static Evaluator makeEvaluator(Node node, Context context) {
12 | return Evaluator.builder(node)
13 | .addIntrinsic(new FileBytesIntrinsic(context))
14 | .addIntrinsic(new FileContentIntrinsic(context))
15 | .addIntrinsic(new IncludeIntrinsic(context))
16 | .build();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/FileBytesIntrinsic.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.intrinsic;
2 |
3 | import org.quiltmc.chasm.api.util.Context;
4 | import org.quiltmc.chasm.lang.api.ast.Ast;
5 | import org.quiltmc.chasm.lang.api.ast.ListNode;
6 | import org.quiltmc.chasm.lang.api.ast.Node;
7 | import org.quiltmc.chasm.lang.api.ast.StringNode;
8 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
9 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction;
10 | import org.quiltmc.chasm.lang.api.exception.EvaluationException;
11 |
12 | public class FileBytesIntrinsic extends IntrinsicFunction {
13 | private final Context context;
14 |
15 | public FileBytesIntrinsic(Context context) {
16 | this.context = context;
17 | }
18 |
19 | @Override
20 | public Node apply(Evaluator evaluator, Node arg) {
21 | if (!(arg instanceof StringNode)) {
22 | throw new EvaluationException(
23 | "Built-in function \"file_bytes\" can only be applied to strings but found " + arg);
24 | }
25 | byte[] bytes = context.readFile(((StringNode) arg).getValue());
26 | if (bytes == null) {
27 | return Ast.nullNode();
28 | }
29 | ListNode result = Ast.emptyList();
30 | for (byte b : bytes) {
31 | result.add(Ast.literal(b & 0xff));
32 | }
33 | return result;
34 | }
35 |
36 | @Override
37 | public String getName() {
38 | return "file_bytes";
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/FileContentIntrinsic.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.intrinsic;
2 |
3 | import java.nio.charset.StandardCharsets;
4 |
5 | import org.jetbrains.annotations.Nullable;
6 | import org.quiltmc.chasm.api.util.Context;
7 | import org.quiltmc.chasm.lang.api.ast.Ast;
8 | import org.quiltmc.chasm.lang.api.ast.Node;
9 | import org.quiltmc.chasm.lang.api.ast.StringNode;
10 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
11 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction;
12 | import org.quiltmc.chasm.lang.api.exception.EvaluationException;
13 |
14 | public class FileContentIntrinsic extends IntrinsicFunction {
15 | private final Context context;
16 |
17 | public FileContentIntrinsic(Context context) {
18 | this.context = context;
19 | }
20 |
21 | @Override
22 | public Node apply(Evaluator evaluator, Node arg) {
23 | String content = readString(arg, context);
24 | return Ast.nullableString(content);
25 | }
26 |
27 | @Override
28 | public String getName() {
29 | return "file_content";
30 | }
31 |
32 | @Nullable
33 | static String readString(Node arg, Context context) {
34 | if (!(arg instanceof StringNode)) {
35 | throw new EvaluationException(
36 | "Built-in function \"file_content\" can only be applied to strings but found " + arg);
37 | }
38 | String path = ((StringNode) arg).getValue();
39 | byte[] bytes = context.readFile(path);
40 | if (bytes == null) {
41 | return null;
42 | }
43 | try {
44 | return new String(bytes, StandardCharsets.UTF_8);
45 | } catch (IllegalArgumentException e) {
46 | throw new EvaluationException("File \"" + path + "\" is not utf8 encoded");
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/intrinsic/IncludeIntrinsic.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.intrinsic;
2 |
3 | import org.quiltmc.chasm.api.util.Context;
4 | import org.quiltmc.chasm.lang.api.ast.Node;
5 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
6 | import org.quiltmc.chasm.lang.api.eval.IntrinsicFunction;
7 | import org.quiltmc.chasm.lang.api.exception.EvaluationException;
8 | import org.quiltmc.chasm.lang.api.exception.ParseException;
9 |
10 | public class IncludeIntrinsic extends IntrinsicFunction {
11 | private final Context context;
12 |
13 | public IncludeIntrinsic(Context context) {
14 | this.context = context;
15 | }
16 |
17 | @Override
18 | public Node apply(Evaluator evaluator, Node arg) {
19 | String content = FileContentIntrinsic.readString(arg, context);
20 | if (content == null) {
21 | throw new EvaluationException("Could not read file " + arg);
22 | }
23 | Node result;
24 | try {
25 | result = Node.parse(content);
26 | } catch (ParseException e) {
27 | throw new EvaluationException("Failed to parse file " + arg + ": " + e.getMessage());
28 | }
29 | result.resolve(evaluator.getResolver());
30 | return result.evaluate(evaluator);
31 | }
32 |
33 | @Override
34 | public String getName() {
35 | return "include";
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/metadata/OriginMetadata.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.metadata;
2 |
3 | import org.quiltmc.chasm.api.Transformation;
4 |
5 | public class OriginMetadata {
6 | private final String transformerId;
7 |
8 | public OriginMetadata(Transformation origin) {
9 | this(origin.getParent().getId());
10 | }
11 |
12 | private OriginMetadata(String transformerId) {
13 | this.transformerId = transformerId;
14 | }
15 |
16 | public String getTransformerId() {
17 | return transformerId;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/metadata/package-info.java:
--------------------------------------------------------------------------------
1 | @ApiStatus.Internal
2 | package org.quiltmc.chasm.internal.metadata;
3 |
4 | import org.jetbrains.annotations.ApiStatus;
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/package-info.java:
--------------------------------------------------------------------------------
1 | @ApiStatus.Internal
2 | package org.quiltmc.chasm.internal;
3 |
4 | import org.jetbrains.annotations.ApiStatus;
5 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/transformer/ChasmLangTransformation.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.transformer;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | import org.quiltmc.chasm.api.Transformation;
7 | import org.quiltmc.chasm.api.Transformer;
8 | import org.quiltmc.chasm.api.target.NodeTarget;
9 | import org.quiltmc.chasm.api.target.SliceTarget;
10 | import org.quiltmc.chasm.api.target.Target;
11 | import org.quiltmc.chasm.internal.util.NodeUtils;
12 | import org.quiltmc.chasm.lang.api.ast.Ast;
13 | import org.quiltmc.chasm.lang.api.ast.CallNode;
14 | import org.quiltmc.chasm.lang.api.ast.IntegerNode;
15 | import org.quiltmc.chasm.lang.api.ast.ListNode;
16 | import org.quiltmc.chasm.lang.api.ast.MapNode;
17 | import org.quiltmc.chasm.lang.api.ast.Node;
18 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
19 | import org.quiltmc.chasm.lang.api.eval.FunctionNode;
20 |
21 | public class ChasmLangTransformation implements Transformation {
22 | private final Transformer parent;
23 | private final Evaluator evaluator;
24 | private final FunctionNode apply;
25 | private final Target target;
26 | private final Map sources = new LinkedHashMap<>();
27 |
28 | public ChasmLangTransformation(Transformer parent, Node node, Evaluator evaluator) {
29 | this.parent = parent;
30 | this.evaluator = evaluator;
31 |
32 | if (!(node instanceof MapNode)) {
33 | throw new RuntimeException("Transformations must be maps");
34 | }
35 |
36 | MapNode transformationExpression = (MapNode) node;
37 |
38 | Node targetNode = transformationExpression.get("target");
39 | if (!(targetNode instanceof MapNode)) {
40 | throw new RuntimeException("Transformations must declare a map \"target\" in their root map");
41 | }
42 | this.target = parseTarget((MapNode) targetNode);
43 |
44 | Node applyNode = transformationExpression.get("apply");
45 | if (!(applyNode instanceof FunctionNode)) {
46 | throw new RuntimeException("Transformations must declare a function \"apply\" in their root map");
47 | }
48 | this.apply = (FunctionNode) applyNode;
49 |
50 | Node sourcesNode = transformationExpression.get("sources");
51 | if (sourcesNode != null) {
52 | if (!(sourcesNode instanceof MapNode)) {
53 | throw new RuntimeException("Element \"sources\" in transformation must be a map");
54 | }
55 |
56 | for (Map.Entry entry : ((MapNode) sourcesNode).getEntries().entrySet()) {
57 | if (!(entry.getValue() instanceof MapNode)) {
58 | throw new RuntimeException("Transformation sources must be maps");
59 | }
60 |
61 | Target source = parseTarget((MapNode) entry.getValue());
62 | this.sources.put(entry.getKey(), source);
63 | }
64 | }
65 | }
66 |
67 | @Override
68 | public Transformer getParent() {
69 | return parent;
70 | }
71 |
72 | @Override
73 | public Target getTarget() {
74 | return target;
75 | }
76 |
77 | @Override
78 | public Map getSources() {
79 | return sources;
80 | }
81 |
82 | @Override
83 | public Node apply(Node targetNode, Map nodeSources) {
84 | MapNode args = Ast.map()
85 | .put("target", targetNode)
86 | .put("sources", nodeSources)
87 | .build();
88 | CallNode callExpression = Ast.call(apply, args);
89 | return callExpression.evaluate(evaluator);
90 | }
91 |
92 | private Target parseTarget(MapNode target) {
93 | Node targetNode = NodeUtils.get(target, "node");
94 |
95 | Integer start = null;
96 | Node startNode = NodeUtils.get(target, "start");
97 | if (startNode instanceof IntegerNode) {
98 | start = ((IntegerNode) startNode).getValue().intValue();
99 | }
100 |
101 | Integer end = null;
102 | Node endNode = NodeUtils.get(target, "end");
103 | if (endNode instanceof IntegerNode) {
104 | end = ((IntegerNode) endNode).getValue().intValue();
105 | }
106 |
107 | if (targetNode instanceof ListNode && start != null && end != null) {
108 | return new SliceTarget((ListNode) targetNode, start, end);
109 | } else {
110 | return new NodeTarget(targetNode);
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/transformer/ChasmLangTransformer.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.transformer;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 |
6 | import org.quiltmc.chasm.api.Transformation;
7 | import org.quiltmc.chasm.api.Transformer;
8 | import org.quiltmc.chasm.api.util.Context;
9 | import org.quiltmc.chasm.internal.intrinsic.ChasmIntrinsics;
10 | import org.quiltmc.chasm.lang.api.ast.Ast;
11 | import org.quiltmc.chasm.lang.api.ast.CallNode;
12 | import org.quiltmc.chasm.lang.api.ast.LambdaNode;
13 | import org.quiltmc.chasm.lang.api.ast.ListNode;
14 | import org.quiltmc.chasm.lang.api.ast.MapNode;
15 | import org.quiltmc.chasm.lang.api.ast.Node;
16 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
17 |
18 | public class ChasmLangTransformer implements Transformer {
19 | private final Node parsed;
20 |
21 | private final String id;
22 |
23 | private final Context context;
24 |
25 | public ChasmLangTransformer(String id, Node parsed, Context context) {
26 | this.id = id;
27 | this.parsed = parsed;
28 | this.context = context;
29 | }
30 |
31 | @Override
32 | public Collection apply(ListNode classes) {
33 | LambdaNode lambdaExpression = Ast.lambda("classes", parsed);
34 | CallNode callExpression = Ast.call(lambdaExpression, classes);
35 |
36 | Evaluator evaluator = ChasmIntrinsics.makeEvaluator(callExpression, context);
37 | Node evaluated = callExpression.evaluate(evaluator);
38 | if (!(evaluated instanceof MapNode)) {
39 | throw new RuntimeException("Transformers must be maps");
40 | }
41 |
42 | MapNode transformerExpression = (MapNode) evaluated;
43 | Node transformationsNode = transformerExpression.get("transformations");
44 | if (!(transformationsNode instanceof ListNode)) {
45 | throw new RuntimeException("Transformers must declare a list \"transformations\" in their root map");
46 | }
47 |
48 | ArrayList transformations = new ArrayList<>();
49 | for (Node entry : ((ListNode) transformationsNode).getEntries()) {
50 | transformations.add(new ChasmLangTransformation(this, entry, evaluator));
51 | }
52 |
53 | return transformations;
54 | }
55 |
56 | @Override
57 | public String getId() {
58 | return id;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/transformer/package-info.java:
--------------------------------------------------------------------------------
1 | @ApiStatus.Internal
2 | package org.quiltmc.chasm.internal.transformer;
3 |
4 | import org.jetbrains.annotations.ApiStatus;
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/tree/ClassNode.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.tree;
2 |
3 | import java.util.LinkedHashMap;
4 | import java.util.Map;
5 |
6 | import org.objectweb.asm.ClassReader;
7 | import org.quiltmc.chasm.api.util.Context;
8 | import org.quiltmc.chasm.internal.asm.visitor.ChasmClassVisitor;
9 | import org.quiltmc.chasm.internal.metadata.PathMetadata;
10 | import org.quiltmc.chasm.internal.util.NodeConstants;
11 | import org.quiltmc.chasm.internal.util.PathInitializer;
12 | import org.quiltmc.chasm.lang.api.ast.Ast;
13 | import org.quiltmc.chasm.lang.api.ast.MapNode;
14 | import org.quiltmc.chasm.lang.api.ast.Node;
15 | import org.quiltmc.chasm.lang.api.eval.Evaluator;
16 | import org.quiltmc.chasm.lang.api.eval.Resolver;
17 |
18 | public class ClassNode extends MapNode {
19 | private final ClassReader reader;
20 |
21 | public ClassNode(ClassReader reader, Context context, int index) {
22 | super(new LazyMap<>(getStaticEntries(reader),
23 | () -> getLazyEntries(reader, context, index)));
24 | this.reader = reader;
25 |
26 | PathMetadata root = new PathMetadata(null, index);
27 | PathInitializer.initialize(this, root);
28 | }
29 |
30 | // NOTE: Ensure parity with ChasmClassVisitor
31 | private static Map getStaticEntries(ClassReader reader) {
32 | Map entries = new LinkedHashMap<>();
33 |
34 | entries.put(NodeConstants.ACCESS, Ast.literal(reader.getAccess()));
35 | entries.put(NodeConstants.NAME, Ast.literal(reader.getClassName()));
36 | entries.put(NodeConstants.SUPER, Ast.nullableString(reader.getSuperName()));
37 | entries.put(NodeConstants.INTERFACES, Ast.list((Object[]) reader.getInterfaces()));
38 |
39 | return entries;
40 | }
41 |
42 | public Map getStaticEntries() {
43 | return ((LazyMap) getEntries()).getStaticEntries();
44 | }
45 |
46 | public Map getLazyEntries() {
47 | return ((LazyMap) getEntries()).getLazyEntries();
48 | }
49 |
50 | private static Map getLazyEntries(ClassReader reader, Context context,
51 | int index) {
52 | ChasmClassVisitor visitor = new ChasmClassVisitor(context);
53 | reader.accept(visitor, 0);
54 | Map entries = visitor.getClassNode().getEntries();
55 |
56 | PathMetadata root = new PathMetadata(null, index);
57 | for (Map.Entry entry : entries.entrySet()) {
58 | PathMetadata path = new PathMetadata(root, entry.getKey());
59 | PathInitializer.initialize(entry.getValue(), path);
60 | }
61 |
62 | return entries;
63 | }
64 |
65 | public ClassReader getClassReader() {
66 | return reader;
67 | }
68 |
69 | @Override
70 | public void resolve(Resolver resolver) {
71 | // Class node can't contain references
72 | }
73 |
74 | @Override
75 | public Node evaluate(Evaluator evaluator) {
76 | // Class node can't be further simplified
77 | return this;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/chasm/src/main/java/org/quiltmc/chasm/internal/tree/LazyMap.java:
--------------------------------------------------------------------------------
1 | package org.quiltmc.chasm.internal.tree;
2 |
3 | import java.lang.ref.SoftReference;
4 | import java.util.Collection;
5 | import java.util.Map;
6 | import java.util.Set;
7 | import java.util.function.Supplier;
8 |
9 | import org.jetbrains.annotations.NotNull;
10 | import org.jetbrains.annotations.Nullable;
11 |
12 | public class LazyMap implements Map {
13 | private final Map staticEntries;
14 | private final Supplier