├── .gitignore
├── README.md
├── annotations
├── pom.xml
└── src
│ └── main
│ └── java
│ └── j2cc
│ ├── AlwaysInline.java
│ ├── Exclude.java
│ └── Nativeify.java
├── assembly
├── core.xml
├── start.ps1
└── start.sh
├── core
├── dependency-reduced-pom.xml
├── pom.xml
└── src
│ ├── main
│ ├── java
│ │ └── me
│ │ │ └── x150
│ │ │ └── j2cc
│ │ │ ├── Entry.java
│ │ │ ├── J2CC.java
│ │ │ ├── analysis
│ │ │ ├── Block.java
│ │ │ ├── BlockList.java
│ │ │ └── UniqueList.java
│ │ │ ├── compiler
│ │ │ ├── CacheSlotManager.java
│ │ │ ├── CompilerContext.java
│ │ │ ├── CompilerEngine.java
│ │ │ ├── CompilerJob.java
│ │ │ ├── DefaultCompiler.java
│ │ │ ├── MemberCache.java
│ │ │ └── handler
│ │ │ │ ├── FieldInsnNodeHandler.java
│ │ │ │ ├── IincInsnNodeHandler.java
│ │ │ │ ├── InsnHandler.java
│ │ │ │ ├── InsnNodeHandler.java
│ │ │ │ ├── IntInsnNodeHandler.java
│ │ │ │ ├── InvokeDynamicHandler.java
│ │ │ │ ├── JumpInsnNodeHandler.java
│ │ │ │ ├── LabelNodeHandler.java
│ │ │ │ ├── LdcInsnHandler.java
│ │ │ │ ├── LookupSwitchInsnHandler.java
│ │ │ │ ├── MethodInsnHandler.java
│ │ │ │ ├── MultiANewArrayInsnHandler.java
│ │ │ │ ├── NoopHandler.java
│ │ │ │ ├── TableSwitchInsnHandler.java
│ │ │ │ ├── TypeInsnNodeHandler.java
│ │ │ │ └── VarInsnNodeHandler.java
│ │ │ ├── compilerExec
│ │ │ ├── Compiler.java
│ │ │ ├── GccCompiler.java
│ │ │ └── ZigCompiler.java
│ │ │ ├── conf
│ │ │ ├── Configuration.java
│ │ │ ├── Context.java
│ │ │ └── javaconf
│ │ │ │ ├── Configurable.java
│ │ │ │ ├── ConfigurationManager.java
│ │ │ │ ├── Util.java
│ │ │ │ └── annots
│ │ │ │ ├── ConfigValue.java
│ │ │ │ ├── DTOConfigurable.java
│ │ │ │ └── MapConfigurable.java
│ │ │ ├── cppwriter
│ │ │ ├── Include.java
│ │ │ ├── Method.java
│ │ │ ├── Printable.java
│ │ │ ├── SourceBuilder.java
│ │ │ └── SwitchCaseCodeSegment.java
│ │ │ ├── exc
│ │ │ └── CompilationFailure.java
│ │ │ ├── input
│ │ │ ├── DirectoryInputProvider.java
│ │ │ ├── InputProvider.java
│ │ │ └── JarInputProvider.java
│ │ │ ├── obfuscator
│ │ │ ├── ObfuscationContext.java
│ │ │ ├── Obfuscator.java
│ │ │ ├── ObfuscatorPass.java
│ │ │ ├── etc
│ │ │ │ ├── ExtractConstructors.java
│ │ │ │ └── RemoveDebugInfo.java
│ │ │ ├── optim
│ │ │ │ ├── BranchInliner.java
│ │ │ │ ├── EliminateDeadCode.java
│ │ │ │ ├── Inliner.java
│ │ │ │ ├── OptimizerPass.java
│ │ │ │ └── PushOptimizer.java
│ │ │ ├── refs
│ │ │ │ └── MhCallRef.java
│ │ │ └── strings
│ │ │ │ ├── StringDecoder.java
│ │ │ │ └── StringObfuscator.java
│ │ │ ├── optimizer
│ │ │ ├── LocalPropagator.java
│ │ │ ├── Pass.java
│ │ │ ├── PopInliner.java
│ │ │ ├── RemoveRedundantLabelsPass.java
│ │ │ ├── RemoveUnusedVars.java
│ │ │ └── ValueInliner.java
│ │ │ ├── output
│ │ │ ├── DirectoryOutputSink.java
│ │ │ ├── FsOutputSink.java
│ │ │ ├── JarOutputSink.java
│ │ │ └── OutputSink.java
│ │ │ ├── tree
│ │ │ ├── AsmClassInfo.java
│ │ │ ├── AsmFieldInfo.java
│ │ │ ├── AsmMethodInfo.java
│ │ │ ├── Pair.java
│ │ │ ├── Remapper.java
│ │ │ ├── SmartClassWriter.java
│ │ │ ├── Workspace.java
│ │ │ └── resolver
│ │ │ │ ├── DirectoryResolver.java
│ │ │ │ ├── FsResolver.java
│ │ │ │ ├── JmodResolver.java
│ │ │ │ ├── Resolver.java
│ │ │ │ └── UnionResolver.java
│ │ │ └── util
│ │ │ ├── ClassFilter.java
│ │ │ ├── Graph.java
│ │ │ ├── InstructionPatchList.java
│ │ │ ├── InvalidCodeGuard.java
│ │ │ ├── MappingSet.java
│ │ │ ├── MemberFilter.java
│ │ │ ├── MethodInliner.java
│ │ │ ├── NameGenerator.java
│ │ │ ├── Pair.java
│ │ │ ├── StringCollector.java
│ │ │ ├── Util.java
│ │ │ ├── natives
│ │ │ └── chacha20_h.java
│ │ │ └── simulation
│ │ │ ├── SimulatorInterpreter.java
│ │ │ └── SimulatorValue.java
│ └── resources
│ │ ├── log4j2.xml
│ │ ├── logo.txt
│ │ ├── memo.txt
│ │ └── user.xsd
│ └── test
│ ├── java
│ └── me
│ │ └── x150
│ │ └── j2cc
│ │ ├── CoreTests.java
│ │ └── util
│ │ ├── InstructionPatchListTest.java
│ │ └── RemapperTest.java
│ └── resources
│ ├── createTest.sh
│ ├── junit-platform.properties
│ └── tests
│ ├── testArrays
│ ├── testArrays.jasm
│ ├── testArrays.output
│ └── testArrays.test.json
│ ├── testCasting
│ ├── testCasting.jasm
│ ├── testCasting.output
│ └── testCasting.test.json
│ ├── testConstantDynamic
│ ├── testConstantDynamic.jasm
│ ├── testConstantDynamic.output
│ └── testConstantDynamic.test.json
│ ├── testConstantObf
│ ├── testConstantObf.java
│ ├── testConstantObf.output
│ └── testConstantObf.test.json
│ ├── testControlFlow
│ ├── testControlFlow.java
│ ├── testControlFlow.output
│ └── testControlFlow.test.json
│ ├── testConversion
│ ├── testConversion.jasm
│ ├── testConversion.output
│ └── testConversion.test.json
│ ├── testDups
│ ├── testDups.jasm
│ ├── testDups.output
│ └── testDups.test.json
│ ├── testFields
│ ├── testFields.java
│ ├── testFields.output
│ └── testFields.test.json
│ ├── testFinally
│ ├── testFinally.java
│ ├── testFinally.output
│ └── testFinally.test.json
│ ├── testHandleLdc
│ ├── testHandleLdc.jasm
│ ├── testHandleLdc.output
│ └── testHandleLdc.test.json
│ ├── testHelloWorld
│ ├── testHelloWorld.jasm
│ ├── testHelloWorld.output
│ └── testHelloWorld.test.json
│ ├── testLambdas
│ ├── testLambdas.java
│ ├── testLambdas.output
│ └── testLambdas.test.json
│ ├── testLocals
│ ├── testLocals.jasm
│ ├── testLocals.output
│ └── testLocals.test.json
│ ├── testLoops
│ ├── testLoops.java
│ ├── testLoops.output
│ └── testLoops.test.json
│ ├── testMath
│ ├── testMath.jasm
│ ├── testMath.output
│ └── testMath.test.json
│ ├── testMethodHandles
│ ├── testMethodHandles.java
│ ├── testMethodHandles.output
│ └── testMethodHandles.test.json
│ ├── testObfuscation
│ ├── testObfuscation.java
│ ├── testObfuscation.output
│ └── testObfuscation.test.json
│ ├── testPropagatingExceptions
│ ├── testPropagatingExceptions.java
│ ├── testPropagatingExceptions.output
│ └── testPropagatingExceptions.test.json
│ ├── testReturnInFinally
│ ├── testReturnInFinally.java
│ ├── testReturnInFinally.output
│ └── testReturnInFinally.test.json
│ ├── testStringOps
│ ├── testStringOps.java
│ ├── testStringOps.output
│ └── testStringOps.test.json
│ └── testTryCatch
│ ├── testTryCatch.java
│ ├── testTryCatch.output
│ └── testTryCatch.test.json
├── createDist.sh
├── internals
├── pom.xml
└── src
│ └── main
│ └── java
│ └── j2cc
│ └── internal
│ ├── Debug.java
│ ├── Loader.java
│ └── Platform.java
├── j2cc-maven-plugin
├── pom.xml
└── src
│ └── main
│ └── java
│ └── me
│ └── x150
│ └── j2cc
│ └── ObfuscateMojo.java
├── pom.xml
├── testjar
├── dependency-reduced-pom.xml
├── pom.xml
└── src
│ └── main
│ ├── java
│ └── me
│ │ └── x150
│ │ └── j2cc
│ │ ├── Main.java
│ │ ├── MethodSimTest.java
│ │ ├── SimTest.java
│ │ ├── StackTranslator.java
│ │ ├── a2.java
│ │ ├── clSplit
│ │ ├── A.java
│ │ ├── B.java
│ │ └── C.java
│ │ └── inheri
│ │ ├── Child.java
│ │ ├── Parent.java
│ │ └── SecondChild.java
│ └── resources
│ └── a.txt
└── util
├── CMakeLists.txt
├── antiHook.cpp
├── antiHook.h
├── build_comptime_library.sh
├── chacha20.cpp
├── chacha20.h
├── export.h
├── test_main.cpp
├── util.cpp
└── util.h
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 | !**/src/main/**/target/
4 | !**/src/test/**/target/
5 |
6 | ### IntelliJ IDEA ###
7 | .idea/modules.xml
8 | .idea/jarRepositories.xml
9 | .idea/compiler.xml
10 | .idea/libraries/
11 | *.iws
12 | *.iml
13 | *.ipr
14 |
15 | ### Eclipse ###
16 | .apt_generated
17 | .classpath
18 | .factorypath
19 | .project
20 | .settings
21 | .springBeans
22 | .sts4-cache
23 |
24 | ### NetBeans ###
25 | /nbproject/private/
26 | /nbbuild/
27 | #/dist/
28 | /nbdist/
29 | /.nb-gradle/
30 | build/
31 | !**/src/main/**/build/
32 | !**/src/test/**/build/
33 |
34 | ### VS Code ###
35 | .vscode/
36 |
37 | ### Mac OS ###
38 | .DS_Store
39 |
40 | .idea/
41 | ### VS Code ###
42 | .settings/
43 |
44 | ### Mac OS ###
45 |
46 | #Gradle
47 | .gradle/
48 | out/
49 | classes/
50 |
51 | zig-compiler
52 | util/cmake-build-debug
53 |
54 | # temp directory for tests
55 | core/temp
56 | work/
57 |
58 | j2ccLicense.bin
59 | /dist_package/
60 |
61 | tmp
62 |
63 | /natives
64 |
--------------------------------------------------------------------------------
/annotations/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.x150.j2cc
8 | j2cc-bom
9 | 1.0-SNAPSHOT
10 |
11 |
12 | ${j2cc.version}
13 |
14 | annotations
15 |
16 |
17 | 11
18 | 11
19 | UTF-8
20 |
21 |
22 |
--------------------------------------------------------------------------------
/annotations/src/main/java/j2cc/AlwaysInline.java:
--------------------------------------------------------------------------------
1 | package j2cc;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.CLASS)
9 | @Target(ElementType.METHOD)
10 | public @interface AlwaysInline {
11 | boolean removeOriginal() default false;
12 | }
13 |
--------------------------------------------------------------------------------
/annotations/src/main/java/j2cc/Exclude.java:
--------------------------------------------------------------------------------
1 | package j2cc;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.CLASS)
9 | @Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE, ElementType.PACKAGE})
10 | public @interface Exclude {
11 | From[] value();
12 |
13 | enum From {
14 | RENAMING, OBFUSCATION, COMPILATION
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/annotations/src/main/java/j2cc/Nativeify.java:
--------------------------------------------------------------------------------
1 | package j2cc;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target({ElementType.METHOD, ElementType.TYPE})
9 | @Retention(RetentionPolicy.CLASS)
10 | public @interface Nativeify {
11 | }
12 |
--------------------------------------------------------------------------------
/assembly/core.xml:
--------------------------------------------------------------------------------
1 |
4 | core
5 |
6 | dir
7 |
8 | false
9 |
10 |
11 | /
12 | true
13 | false
14 |
15 | me.x150.j2cc:core
16 | me.x150.j2cc:annotations
17 |
18 |
19 |
20 | libs
21 | false
22 | false
23 | runtime
24 |
25 |
26 |
27 |
28 | ../assembly
29 | 755
30 | .
31 | true
32 |
33 | start.sh
34 | start.ps1
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/assembly/start.ps1:
--------------------------------------------------------------------------------
1 | # Check for JAVA_HOME, fallback to java in PATH
2 | if ($env:JAVA_HOME) {
3 | $javaCmd = Join-Path $env:JAVA_HOME "bin\java.exe"
4 | } else {
5 | $javaCmd = "java.exe"
6 | }
7 |
8 | # Check if Java is accessible
9 | $null = & $javaCmd -version 2>$null
10 | if ($LASTEXITCODE -ne 0) {
11 | Write-Error "Java not found. Please set JAVA_HOME or ensure java.exe is in your PATH."
12 | exit 1
13 | }
14 |
15 | # Launch the Java application with the required argument
16 | $env:PATH = "$PSScriptRoot/natives;$env:PATH"
17 | & $javaCmd --enable-native-access=ALL-UNNAMED -jar "$PSScriptRoot/j2cc-@version@.jar" @args
18 |
--------------------------------------------------------------------------------
/assembly/start.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # Check for JAVA_HOME, fallback to java in PATH
4 | if [[ -n "$JAVA_HOME" ]]; then
5 | JAVA_CMD="$JAVA_HOME/bin/java"
6 | else
7 | JAVA_CMD="java"
8 | fi
9 |
10 | # Ensure Java is available
11 | if ! command -v "$JAVA_CMD" &> /dev/null; then
12 | echo "Error: Java not found. Please set JAVA_HOME or ensure 'java' is in your PATH."
13 | exit 1
14 | fi
15 |
16 | CPATH=$(realpath "$(dirname "$0")")
17 |
18 | LD_LIBRARY_PATH="$CPATH/natives:./natives" "$JAVA_CMD" --enable-native-access=ALL-UNNAMED -jar "$CPATH/core-@version@.jar" "$@"
19 |
--------------------------------------------------------------------------------
/core/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | j2cc-bom
5 | me.x150.j2cc
6 | 1.0-SNAPSHOT
7 |
8 | 4.0.0
9 | core
10 | ${j2cc.version}
11 |
12 |
13 |
14 | maven-surefire-plugin
15 | 3.1.2
16 |
17 |
18 | maven-jar-plugin
19 | 3.3.0
20 |
21 |
22 |
23 | me.x150.j2cc.Entry
24 |
25 |
26 |
27 |
28 |
29 | maven-shade-plugin
30 | 3.4.1
31 |
32 |
33 | package
34 |
35 | shade
36 |
37 |
38 |
39 |
40 | org.projectlombok:*
41 |
42 |
43 |
44 |
45 | me.x150.j2cc.Entry
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | jitpack.io
57 | https://jitpack.io
58 |
59 |
60 |
61 |
62 | org.projectlombok
63 | lombok
64 | 1.18.30
65 | compile
66 |
67 |
68 | org.junit.jupiter
69 | junit-jupiter
70 | 5.10.0
71 | test
72 |
73 |
74 | junit-jupiter-api
75 | org.junit.jupiter
76 |
77 |
78 | junit-jupiter-params
79 | org.junit.jupiter
80 |
81 |
82 | junit-jupiter-engine
83 | org.junit.jupiter
84 |
85 |
86 |
87 |
88 | org.junit.platform
89 | junit-platform-engine
90 | 1.10.0
91 | test
92 |
93 |
94 | opentest4j
95 | org.opentest4j
96 |
97 |
98 | junit-platform-commons
99 | org.junit.platform
100 |
101 |
102 | apiguardian-api
103 | org.apiguardian
104 |
105 |
106 |
107 |
108 | com.google.code.gson
109 | gson
110 | 2.10.1
111 | test
112 |
113 |
114 | com.google.jimfs
115 | jimfs
116 | 1.3.0
117 | test
118 |
119 |
120 | guava
121 | com.google.guava
122 |
123 |
124 |
125 |
126 |
127 | 21
128 | 21
129 | UTF-8
130 |
131 |
132 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/analysis/Block.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.analysis;
2 |
3 | import lombok.AccessLevel;
4 | import lombok.Getter;
5 | import lombok.RequiredArgsConstructor;
6 | import lombok.experimental.FieldDefaults;
7 | import me.x150.j2cc.util.Util;
8 | import org.objectweb.asm.tree.*;
9 |
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Objects;
13 |
14 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
15 | @FieldDefaults(level = AccessLevel.PUBLIC)
16 | public class Block {
17 | @Getter
18 | List nodes;
19 | UniqueList comesFrom;
20 | UniqueList jumpsTo;
21 | UniqueList exceptionHandlers;
22 | Block flowsTo;
23 | ExceptionHandlerState excHandlerState;
24 |
25 | @SuppressWarnings("CanBeFinal")
26 | public String debugInfo = "";
27 |
28 | public Block(List nodes, UniqueList comesFrom, UniqueList jumpsTo, UniqueList exceptionHandlers, Block flowsTo, ExceptionHandlerState state) {
29 | this.nodes = nodes;
30 | this.comesFrom = comesFrom;
31 | this.jumpsTo = jumpsTo;
32 | this.flowsTo = flowsTo;
33 | this.exceptionHandlers = exceptionHandlers;
34 | this.excHandlerState = state;
35 | }
36 |
37 | public String stringify(Map lb) {
38 | StringBuilder sb = new StringBuilder();
39 | List abstractInsnNodes = this.nodes;
40 | for (AbstractInsnNode node : abstractInsnNodes) {
41 | String i = Util.stringifyInstruction(node, lb);
42 | if (i.isBlank()) continue;
43 | sb.append(i);
44 | sb.append("\n");
45 | }
46 | sb.append(debugInfo);
47 | return sb.toString().trim();
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return String.format(
53 | "%s{excHandlerState=%s}", getClass().getSimpleName(), this.excHandlerState);
54 | }
55 |
56 | public boolean canMergeSuccessor(Block nextBlock) {
57 | return this.flowsTo == nextBlock // we flow into the next block
58 | && nextBlock.comesFrom.size() == 1 && nextBlock.comesFrom.getFirst() == this // the next block only follows us
59 | && this.jumpsTo.isEmpty() // we don't jump anywhere else
60 | && Objects.equals(nextBlock.excHandlerState, this.excHandlerState); // we're part of the same exception handler
61 | }
62 |
63 | public Block merge(Block that, List allBlocks) {
64 | if (!canMergeSuccessor(that)) throw new IllegalStateException("can't merge");
65 | this.nodes.addAll(that.nodes);
66 | this.jumpsTo = that.jumpsTo;
67 | this.flowsTo = that.flowsTo;
68 | for (Block allBlock : allBlocks) {
69 | if (allBlock.flowsTo == that) allBlock.flowsTo = this;
70 | if (allBlock.jumpsTo.contains(that)) allBlock.jumpsTo.replaceAll(block -> block == that ? this : block);
71 | if (allBlock.comesFrom.contains(that)) allBlock.comesFrom.replaceAll(block -> block == that ? this : block);
72 | }
73 | return this;
74 | }
75 |
76 | public record ExceptionHandlerState(TryCatchBlockNode node, boolean isHandler) {
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/analysis/UniqueList.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.analysis;
2 |
3 | import org.jetbrains.annotations.Contract;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.util.*;
7 |
8 | public class UniqueList implements List {
9 | private final transient ArrayList backingList = new ArrayList<>();
10 |
11 | @Override
12 | public int size() {
13 | return backingList.size();
14 | }
15 |
16 | @Override
17 | public boolean isEmpty() {
18 | return backingList.isEmpty();
19 | }
20 |
21 | @Override
22 | public boolean contains(Object o) {
23 | return backingList.contains(o);
24 | }
25 |
26 | @Override
27 | @NotNull
28 | public Iterator iterator() {
29 | return backingList.iterator();
30 | }
31 |
32 | @Override
33 | public Object @NotNull [] toArray() {
34 | return backingList.toArray();
35 | }
36 |
37 | @Override
38 | public T1 @NotNull [] toArray(T1 @NotNull [] a) {
39 | return backingList.toArray(a);
40 | }
41 |
42 | @Override
43 | public boolean add(T t) {
44 | if (backingList.contains(t)) //noinspection Contract
45 | return false;
46 | return backingList.add(t);
47 | }
48 |
49 | @Override
50 | public boolean remove(Object o) {
51 | return backingList.remove(o);
52 | }
53 |
54 | @Override
55 | public boolean containsAll(@NotNull Collection> c) {
56 | return backingList.containsAll(c);
57 | }
58 |
59 | @Override
60 | public boolean addAll(Collection extends T> c) {
61 | boolean any = false;
62 | for (T t : c) {
63 | if (backingList.contains(t)) continue;
64 | backingList.add(t);
65 | any = true;
66 | }
67 | return any;
68 | }
69 |
70 | @Override
71 | public boolean addAll(int index, @NotNull Collection extends T> c) {
72 | return false;
73 | }
74 |
75 | @Override
76 | public boolean removeAll(@NotNull Collection> c) {
77 | return backingList.removeAll(c);
78 | }
79 |
80 | @Override
81 | public boolean retainAll(@NotNull Collection> c) {
82 | return backingList.retainAll(c);
83 | }
84 |
85 | @Override
86 | public void clear() {
87 | backingList.clear();
88 | }
89 |
90 | @Override
91 | public T get(int index) {
92 | return backingList.get(index);
93 | }
94 |
95 | @Override
96 | public T set(int index, T element) {
97 | if (backingList.contains(element)) throw new IllegalArgumentException("Element already present");
98 | return backingList.set(index, element);
99 | }
100 |
101 | @Override
102 | public void add(int index, T element) {
103 | if (backingList.contains(element)) throw new IllegalArgumentException("Element already present");
104 | backingList.add(index, element);
105 | }
106 |
107 | @Override
108 | public T remove(int index) {
109 | return backingList.remove(index);
110 | }
111 |
112 | @Override
113 | public int indexOf(Object o) {
114 | return backingList.indexOf(o);
115 | }
116 |
117 | @Override
118 | public int lastIndexOf(Object o) {
119 | return backingList.lastIndexOf(o);
120 | }
121 |
122 | @Override
123 | public @NotNull ListIterator listIterator() {
124 | return backingList.listIterator();
125 | }
126 |
127 | @Override
128 | public @NotNull ListIterator listIterator(int index) {
129 | return backingList.listIterator(index);
130 | }
131 |
132 | @Override
133 | @Contract("_, _ -> fail")
134 | public @NotNull List subList(int fromIndex, int toIndex) {
135 | throw new UnsupportedOperationException();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/CacheSlotManager.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler;
2 |
3 | import org.objectweb.asm.Handle;
4 | import org.objectweb.asm.Type;
5 |
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.Objects;
9 | import java.util.concurrent.CopyOnWriteArrayList;
10 |
11 | public class CacheSlotManager {
12 | private final CopyOnWriteArrayList specs = new CopyOnWriteArrayList<>();
13 | private final CopyOnWriteArrayList classSlots = new CopyOnWriteArrayList<>();
14 | private final CopyOnWriteArrayList fieldSpecs = new CopyOnWriteArrayList<>();
15 | private final CopyOnWriteArrayList methodSpecs = new CopyOnWriteArrayList<>();
16 |
17 | public synchronized int getOrCreateIndyCacheSlot(InvokeDynamicSpec spec) {
18 | return getOrCreateSlot(specs, spec);
19 | }
20 |
21 | public synchronized int getOrCreateClassSlot(String slot) {
22 | return getOrCreateSlot(classSlots, slot);
23 | }
24 |
25 | public synchronized int getOrCreateFieldSlot(String owner, String name, String desc) {
26 | return getOrCreateSlot(fieldSpecs, new GenericSpec(owner, name, Type.getType(desc)));
27 | }
28 |
29 | public synchronized int getOrCreateMethodSlot(String owner, String name, String desc) {
30 | return getOrCreateSlot(methodSpecs, new GenericSpec(owner, name, Type.getMethodType(desc)));
31 | }
32 |
33 | public int getIndyAmount() {
34 | return specs.size();
35 | }
36 |
37 | public int getClassAmount() {
38 | return classSlots.size();
39 | }
40 |
41 | public int getFieldAmount() {
42 | return fieldSpecs.size();
43 | }
44 |
45 | public int getMethodAmount() {
46 | return methodSpecs.size();
47 | }
48 |
49 | private int getOrCreateSlot(List tc, T val) {
50 | int idx = tc.indexOf(val);
51 | if (idx >= 0) {
52 | return idx;
53 | }
54 | tc.add(val);
55 | return tc.size() - 1;
56 | }
57 |
58 | private record GenericSpec(String owner, String name, Type type) {
59 |
60 | }
61 |
62 | public record InvokeDynamicSpec(Handle bsmHandle, Object[] bsmArgs, String methodName, String methodDesc) {
63 | @Override
64 | public boolean equals(Object o) {
65 | if (this == o) return true;
66 | if (o == null || getClass() != o.getClass()) return false;
67 |
68 | InvokeDynamicSpec that = (InvokeDynamicSpec) o;
69 |
70 | if (!Objects.equals(bsmHandle, that.bsmHandle)) return false;
71 | // Probably incorrect - comparing Object[] arrays with Arrays.equals
72 | if (!Arrays.equals(bsmArgs, that.bsmArgs)) return false;
73 | if (!Objects.equals(methodName, that.methodName)) return false;
74 | return Objects.equals(methodDesc, that.methodDesc);
75 | }
76 |
77 | @Override
78 | public int hashCode() {
79 | int result = bsmHandle != null ? bsmHandle.hashCode() : 0;
80 | result = 31 * result + Arrays.hashCode(bsmArgs);
81 | result = 31 * result + (methodName != null ? methodName.hashCode() : 0);
82 | result = 31 * result + (methodDesc != null ? methodDesc.hashCode() : 0);
83 | return result;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/CompilerEngine.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler;
2 |
3 | import me.x150.j2cc.conf.Context;
4 | import me.x150.j2cc.cppwriter.Method;
5 | import me.x150.j2cc.cppwriter.SourceBuilder;
6 | import me.x150.j2cc.exc.CompilationFailure;
7 | import me.x150.j2cc.tree.Remapper;
8 | import me.x150.j2cc.util.StringCollector;
9 | import org.objectweb.asm.tree.ClassNode;
10 | import org.objectweb.asm.tree.MethodNode;
11 | import org.objectweb.asm.tree.analysis.AnalyzerException;
12 |
13 | public interface CompilerEngine {
14 | Method compile(Context context, ClassNode owner, MethodNode methodNode, SourceBuilder source, String targetSymbol, Remapper remapper, CacheSlotManager indyCache, StringCollector stringCollector) throws AnalyzerException, CompilationFailure;
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/CompilerJob.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler;
2 |
3 | import j2cc.Nativeify;
4 | import lombok.extern.log4j.Log4j2;
5 | import me.x150.j2cc.conf.Context;
6 | import me.x150.j2cc.cppwriter.Method;
7 | import me.x150.j2cc.cppwriter.SourceBuilder;
8 | import me.x150.j2cc.exc.CompilationFailure;
9 | import me.x150.j2cc.tree.Remapper;
10 | import me.x150.j2cc.util.StringCollector;
11 | import me.x150.j2cc.util.Util;
12 | import org.objectweb.asm.Opcodes;
13 | import org.objectweb.asm.Type;
14 | import org.objectweb.asm.tree.ClassNode;
15 | import org.objectweb.asm.tree.MethodNode;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Collection;
19 | import java.util.List;
20 | import java.util.Objects;
21 | import java.util.concurrent.CompletableFuture;
22 | import java.util.concurrent.ExecutorService;
23 | import java.util.stream.Stream;
24 |
25 | // @Nativeify
26 | @Log4j2
27 | public record CompilerJob(ClassNode owner, MethodNode[] toCompile, SourceBuilder header, SourceBuilder[] sources) {
28 | static final String nativeifyDesc = Type.getDescriptor(Nativeify.class);
29 |
30 | public static boolean hasNativeifyAnnotation(ClassNode cn, MethodNode f) {
31 | return Stream.of(f.invisibleAnnotations, f.visibleAnnotations, cn.invisibleAnnotations, cn.visibleAnnotations)
32 | .filter(Objects::nonNull).flatMap(Collection::stream)
33 | .anyMatch(e -> e.desc.equals(nativeifyDesc));
34 | }
35 |
36 | @Nativeify
37 | public List> compile(Remapper remapper, Context context, ExecutorService service, CacheSlotManager indyCache, StringCollector stringCollector) {
38 | List> methodJobs = new ArrayList<>();
39 | for (int i = 0; i < toCompile.length; i++) {
40 | MethodNode methodNode = toCompile[i];
41 | SourceBuilder source = sources[i];
42 | methodJobs.add(CompletableFuture.supplyAsync(() -> {
43 | String fmted = Util.formatMethod(owner.name, new org.objectweb.asm.commons.Method(methodNode.name, methodNode.desc));
44 | log.info("Compiling method {}...", fmted);
45 | String methodName = "j2cc_%s_%s_%d"
46 | .formatted(Util.turnIntoIdentifier(owner.name), methodNode.name.replaceAll("[^a-zA-Z0-9_\\-]", "_"),
47 | owner.methods.indexOf(methodNode));
48 |
49 | Method compile;
50 | try {
51 | compile = context.compiler().compile(context, owner, methodNode, source, methodName, remapper, indyCache, stringCollector);
52 | header.method(compile.getFlags(), compile.getReturns(), compile.name, compile.getParams());
53 | } catch (CompilationFailure cf) {
54 | // pass up
55 | throw cf;
56 | } catch (Throwable e) {
57 | log.error("FUCK: {}.{}{}", owner.name, methodNode.name, methodNode.desc);
58 | throw new RuntimeException(e);
59 | }
60 | methodNode.instructions.clear();
61 | methodNode.access = methodNode.access | Opcodes.ACC_NATIVE;
62 | if (methodNode.localVariables != null) methodNode.localVariables.clear();
63 | return new DefaultCompiler.CompiledMethod(owner.name, methodNode.name, methodNode.desc, methodName, compile);
64 | }, service));
65 | }
66 |
67 | Util.addLoaderInitToClinit(owner, remapper);
68 | return methodJobs;
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/FieldInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.compiler.DefaultCompiler;
5 | import me.x150.j2cc.compiler.MemberCache;
6 | import me.x150.j2cc.conf.Context;
7 | import me.x150.j2cc.cppwriter.Method;
8 | import me.x150.j2cc.util.Util;
9 | import org.objectweb.asm.Type;
10 | import org.objectweb.asm.tree.FieldInsnNode;
11 | import org.objectweb.asm.tree.analysis.Frame;
12 | import org.objectweb.asm.tree.analysis.SourceValue;
13 |
14 | public class FieldInsnNodeHandler implements InsnHandler {
15 | @Override
16 | public void compileInsn(Context context, CompilerContext compilerContext) {
17 | boolean detailed = !context.obfuscationSettings().vagueExceptions();
18 | FieldInsnNode insn = compilerContext.instruction();
19 | int leIndex = compilerContext.instructions().indexOf(insn);
20 | int stackSN = compilerContext.frames()[leIndex].getStackSize();
21 | Frame sourceValueFrame = compilerContext.sourceFrames()[leIndex];
22 | Method m = compilerContext.compileTo();
23 | String ownerClass = insn.owner;
24 | String name = insn.name;
25 | Type fieldType = Type.getType(insn.desc);
26 | String typeName = DefaultCompiler.typeToName.getOrDefault(fieldType, "Object");
27 | char op = Util.typeToTypeChar(fieldType);
28 | switch (insn.getOpcode()) {
29 | case GETSTATIC -> {
30 | String storedClassName = compilerContext.cache().getOrCreateClassResolve(ownerClass, 0);
31 | MemberCache.Descriptor descriptor = new MemberCache.Descriptor(ownerClass, name, insn.desc);
32 | String storedFieldName = compilerContext.cache().getOrCreateStaticFieldFind(descriptor, 0);
33 | m.addStatement("stack[$l].$l = env->GetStatic$lField($l, $l)", stackSN, op, typeName, storedClassName, storedFieldName);
34 | compilerContext.exceptionCheck();
35 | }
36 | case GETFIELD -> {
37 | SourceValue inst = sourceValueFrame.getStack(stackSN - 1);
38 | if (Util.couldBeNull(compilerContext.sourceFrames(), compilerContext.instructions(), inst)) {
39 | m.beginScope("if (!stack[$l].l)", stackSN - 1);
40 | String cc = compilerContext.cache().getOrCreateClassResolve("java/lang/NullPointerException", 0);
41 | m.addStatement("env->ThrowNew($l, $s)", cc, detailed ? "Cannot read field \"" + name + "\"" : "");
42 | compilerContext.exceptionCheck();
43 | m.endScope();
44 | }
45 |
46 | MemberCache.Descriptor descriptor = new MemberCache.Descriptor(ownerClass, name, insn.desc);
47 | String storedFieldName = compilerContext.cache().getOrCreateNonstaticFieldFind(descriptor, 0);
48 | m.addStatement("stack[$l].$l = env->Get$lField(stack[$l].l, $l)", stackSN - 1, op, typeName, stackSN - 1, storedFieldName);
49 | compilerContext.exceptionCheck();
50 | }
51 | case PUTFIELD -> {
52 | SourceValue inst = sourceValueFrame.getStack(stackSN - 2);
53 | if (Util.couldBeNull(compilerContext.sourceFrames(), compilerContext.instructions(), inst)) {
54 | m.beginScope("if (!stack[$l].l)", stackSN - 2);
55 | String cc = compilerContext.cache().getOrCreateClassResolve("java/lang/NullPointerException", 0);
56 | m.addStatement("env->ThrowNew($l, $s)", cc, detailed ? "Cannot assign field \"" + name + "\"" : "");
57 | compilerContext.exceptionCheck();
58 | m.endScope();
59 | }
60 |
61 | MemberCache.Descriptor desc = new MemberCache.Descriptor(ownerClass, name, insn.desc);
62 | String orCreateNonstaticFieldFind = compilerContext.cache().getOrCreateNonstaticFieldFind(desc, 0);
63 | m.addStatement("env->Set$lField(stack[$l].l, $l, stack[$l].$l)", typeName, stackSN - 2, orCreateNonstaticFieldFind, stackSN - 1, op);
64 | compilerContext.exceptionCheck();
65 | }
66 | case PUTSTATIC -> {
67 | String orGenerateClassFind = compilerContext.cache().getOrCreateClassResolve(ownerClass, 0);
68 | MemberCache.Descriptor desc = new MemberCache.Descriptor(ownerClass, name, insn.desc);
69 | String orCreateStaticFieldFind = compilerContext.cache().getOrCreateStaticFieldFind(desc, 0);
70 | m.addStatement("env->SetStatic$lField($l, $l, stack[$l].$l)", typeName, orGenerateClassFind, orCreateStaticFieldFind, stackSN - 1, op);
71 | compilerContext.exceptionCheck();
72 | }
73 | default -> throw Util.unimplemented(insn.getOpcode());
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/IincInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import org.objectweb.asm.Type;
7 | import org.objectweb.asm.tree.IincInsnNode;
8 |
9 | import java.lang.reflect.Modifier;
10 |
11 | public class IincInsnNodeHandler implements InsnHandler {
12 | @Override
13 | public void compileInsn(Context context, CompilerContext compilerContext) {
14 | IincInsnNode insn = compilerContext.instruction();
15 | Method method = compilerContext.compileTo();
16 | int baseArgSizes = (Type.getArgumentsAndReturnSizes(compilerContext.methodNode().desc) >> 2) - 1;
17 | int nrP = baseArgSizes + (Modifier.isStatic(compilerContext.methodNode().access) ? 0 : 1);
18 | boolean isParam = insn.var < nrP;
19 | if (!isParam) {
20 | method.addStatement("locals[$l].i = locals[$l].i + $l", insn.var, insn.var, insn.incr);
21 | } else {
22 | method.addStatement("param$l = param$l + $l", insn.var, insn.var, insn.incr);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/InsnHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.exc.CompilationFailure;
6 | import org.objectweb.asm.Opcodes;
7 | import org.objectweb.asm.tree.AbstractInsnNode;
8 |
9 | public interface InsnHandler extends Opcodes {
10 | void compileInsn(Context context, CompilerContext compilerContext) throws CompilationFailure;
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/IntInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import me.x150.j2cc.util.Util;
7 | import org.objectweb.asm.Type;
8 | import org.objectweb.asm.tree.IntInsnNode;
9 |
10 | public class IntInsnNodeHandler implements InsnHandler {
11 |
12 | private static final String[] ARRAY_TYPE_NAME = {
13 | "Boolean", "Char", "Float", "Double", "Byte", "Short", "Int", "Long"
14 | };
15 |
16 | @Override
17 | public void compileInsn(Context context, CompilerContext compilerContext) {
18 | IntInsnNode insn = compilerContext.instruction();
19 | int stack = compilerContext.frames()[compilerContext.instructions().indexOf(insn)].getStackSize();
20 | final Method m = compilerContext.compileTo();
21 | switch (insn.getOpcode()) {
22 | case BIPUSH, SIPUSH -> m.addStatement("stack[$l].i = $l", stack, insn.operand);
23 | case NEWARRAY -> {
24 | String t = ARRAY_TYPE_NAME[insn.operand - T_BOOLEAN];
25 | m.beginScope("if (stack[$l].i < 0)", stack - 1);
26 | String negArraySize = compilerContext.cache().getOrCreateClassResolve(Type.getInternalName(NegativeArraySizeException.class), 0);
27 | m.addStatement("env->ThrowNew($l, std::to_string(stack[$l].i).c_str())", negArraySize, stack - 1);
28 | compilerContext.exceptionCheck(true);
29 | m.endScope();
30 |
31 | m.addStatement("stack[$l].l = env->New$lArray(stack[$l].i)", stack - 1, t, stack - 1);
32 | }
33 | default -> throw Util.unimplemented(insn.getOpcode());
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/JumpInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import me.x150.j2cc.util.Util;
7 | import org.objectweb.asm.tree.JumpInsnNode;
8 | import org.objectweb.asm.tree.analysis.BasicValue;
9 | import org.objectweb.asm.tree.analysis.Frame;
10 |
11 | public class JumpInsnNodeHandler implements InsnHandler {
12 |
13 | // this is the correct arrangement
14 | private static final String[] C_NUMBER_OPS = {
15 | "==", "!=", "<", ">=", ">", "<="
16 | };
17 |
18 | @Override
19 | public void compileInsn(Context context, CompilerContext compilerContext) {
20 | JumpInsnNode instruction = compilerContext.instruction();
21 | Frame frame = compilerContext.frames()[compilerContext.instructions().indexOf(instruction)];
22 | String labelName = compilerContext.labels().get(instruction.label);
23 | Method m = compilerContext.compileTo();
24 | int stack = frame.getStackSize();
25 | switch (instruction.getOpcode()) {
26 | case GOTO -> m.addStatement("goto $l", labelName);
27 | case IFEQ, IFNE, IFLT, IFGE, IFGT, IFLE -> {
28 | String op = C_NUMBER_OPS[instruction.getOpcode() - IFEQ];
29 | m.beginScope("if (stack[$l].i $l 0)", stack - 1, op);
30 | m.addStatement("goto $l", labelName);
31 | m.endScope();
32 | }
33 | case IF_ICMPEQ, IF_ICMPNE, IF_ICMPLT, IF_ICMPGE, IF_ICMPGT, IF_ICMPLE -> {
34 | String op = C_NUMBER_OPS[instruction.getOpcode() - IF_ICMPEQ];
35 | m.beginScope("if (stack[$l].i $l stack[$l].i)", stack - 2, op, stack - 1);
36 | m.addStatement("goto $l", labelName);
37 | m.endScope();
38 | }
39 | case IF_ACMPEQ, IF_ACMPNE -> {
40 | m.beginScope("if ($lenv->IsSameObject(stack[$l].l, stack[$l].l))", instruction.getOpcode() == IF_ACMPNE ? "!" : "", stack - 2, stack - 1);
41 | m.addStatement("goto $l", labelName);
42 | m.endScope();
43 | }
44 | case IFNULL, IFNONNULL -> {
45 | m.beginScope("if (stack[$l].l $l= nullptr)", stack - 1, instruction.getOpcode() == IFNULL ? "=" : "!");
46 | m.addStatement("goto $l", labelName);
47 | m.endScope();
48 | }
49 | default -> throw Util.unimplemented(instruction.getOpcode());
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/LabelNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import org.objectweb.asm.tree.LabelNode;
6 |
7 | import java.util.Map;
8 |
9 | public class LabelNodeHandler implements InsnHandler {
10 | @Override
11 | public void compileInsn(Context context, CompilerContext compilerContext) {
12 | Map labels = compilerContext.labels();
13 | if (!labels.containsKey(compilerContext.instruction()))
14 | labels.put(compilerContext.instruction(), "lab" + labels.size());
15 | compilerContext.compileTo().add("$l:", labels.get(compilerContext.instruction()));
16 | // we have a label where we could technically jump into
17 | // since we dont know from *where* we jump, or if there's multiple jump points,
18 | // all variables at this point are M(source1, source2, source..., sourceN)
19 | // we cant do much with that information, and im too lazy to actually figure out which are M(sourceX) (=sourceX)
20 | // so just assume all of them are hot now.
21 | compilerContext.compileTo().clearScopes();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/LookupSwitchInsnHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import me.x150.j2cc.cppwriter.Printable;
7 | import me.x150.j2cc.cppwriter.SwitchCaseCodeSegment;
8 | import org.objectweb.asm.tree.LookupSwitchInsnNode;
9 | import org.objectweb.asm.tree.analysis.BasicValue;
10 | import org.objectweb.asm.tree.analysis.Frame;
11 |
12 | import java.util.List;
13 |
14 | public class LookupSwitchInsnHandler implements InsnHandler {
15 | @Override
16 | public void compileInsn(Context context, CompilerContext compilerContext) {
17 | Method method = compilerContext.compileTo();
18 | LookupSwitchInsnNode instruction = compilerContext.instruction();
19 | Frame frame = compilerContext.frames()[compilerContext.instructions().indexOf(instruction)];
20 | int stackHeight = frame.getStackSize();
21 | SwitchCaseCodeSegment s = new SwitchCaseCodeSegment(Printable.formatted("stack[$l].i", stackHeight - 1));
22 | List keys = instruction.keys;
23 | for (int i = 0; i < keys.size(); i++) {
24 | Integer key = keys.get(i);
25 | s.newCase("$l", key);
26 | s.addStmt("goto $l", compilerContext.labels().get(instruction.labels.get(i)));
27 | }
28 | s.dflt();
29 | s.addStmt("goto $l", compilerContext.labels().get(instruction.dflt));
30 | method.addP(s);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/MultiANewArrayInsnHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import org.objectweb.asm.Type;
7 | import org.objectweb.asm.tree.MultiANewArrayInsnNode;
8 |
9 | public class MultiANewArrayInsnHandler implements InsnHandler {
10 |
11 | private static final String[] ARRAY_TYPE_NAMES = {null, "Boolean", "Char", "Byte", "Short", "Int", "Float", "Long", "Double", null, null};
12 |
13 | @Override
14 | public void compileInsn(Context context, CompilerContext compilerContext) {
15 | Method m = compilerContext.compileTo();
16 | MultiANewArrayInsnNode insn = compilerContext.instruction();
17 | int stackSN = compilerContext.frames()[compilerContext.instructions().indexOf(insn)].getStackSize();
18 | int nDims = insn.dims;
19 | Type arrayType = Type.getType(insn.desc);
20 | Type actualFinalArrayType = Type.getType(arrayType.getDescriptor().substring(nDims));
21 | int sort = actualFinalArrayType.getSort();
22 | String suffix = "c";
23 | String arrayTypeName = ARRAY_TYPE_NAMES[sort];
24 | assert arrayTypeName != null;
25 | String negArr = compilerContext.cache().getOrCreateClassResolve(Type.getInternalName(NegativeArraySizeException.class), 0);
26 | for (int i = 0; i < nDims; i++) {
27 | m.beginScope("if (stack[$l].i < 0)", stackSN - nDims + i);
28 | m.addStatement("env->ThrowNew($l, std::to_string(stack[$l].i).c_str())", negArr, stackSN - nDims + i);
29 | compilerContext.exceptionCheck(true);
30 | m.endScope();
31 | }
32 |
33 | for (int i = 0; i < nDims - 1; i++) {
34 | Type type = Type.getType(arrayType.getDescriptor().substring(i + 1));
35 | String clName = type.getSort() == Type.ARRAY ? type.getDescriptor() : type.getInternalName();
36 | String className = compilerContext.cache().getOrCreateClassResolve(clName, 0);
37 |
38 | m.local("jobjectArray", "arr" + i + suffix).initStmt("env->NewObjectArray(stack[$l].i, $l, nullptr)", stackSN - nDims + i, className);
39 | m.beginScope("for (int i$l = 0; i$.0l < stack[$l].i; i$.0l++)", i, stackSN - nDims + i);
40 | }
41 |
42 | if (sort != Type.OBJECT && sort != Type.ARRAY) {
43 | m.local("j" + arrayTypeName.toLowerCase() + "Array", "arr" + (nDims - 1) + suffix).initStmt("env->New$lArray(stack[$l].i)", arrayTypeName, stackSN - 1);
44 | } else {
45 | String n = sort == Type.ARRAY ? actualFinalArrayType.getDescriptor() : actualFinalArrayType.getInternalName();
46 | String resolvedName = compilerContext.cache().getOrCreateClassResolve(n, 0);
47 | m.local("jobjectArray", "arr" + (nDims - 1) + suffix).initStmt("env->NewObjectArray(stack[$l].i, $l, nullptr)", stackSN - 1, resolvedName);
48 | }
49 | m.addStatement("env->SetObjectArrayElement(arr$l$l, i$.0l, arr$l$.1l)", nDims - 2, suffix, nDims - 1);
50 | for (int i = nDims - 1; i > 0; i--) {
51 | m.endScope();
52 | if (i >= 2) m.addStatement("env->SetObjectArrayElement(arr$l$l, i$.0l, arr$l$.1l)", i - 2, suffix, i - 1);
53 | }
54 | m.addStatement("stack[$l].l = arr0$l", stackSN - nDims, suffix);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/NoopHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import org.objectweb.asm.tree.AbstractInsnNode;
6 |
7 | public class NoopHandler implements InsnHandler {
8 | @Override
9 | public void compileInsn(Context context, CompilerContext compilerContext) {
10 |
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/TableSwitchInsnHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import me.x150.j2cc.cppwriter.Printable;
7 | import me.x150.j2cc.cppwriter.SwitchCaseCodeSegment;
8 | import org.objectweb.asm.tree.TableSwitchInsnNode;
9 | import org.objectweb.asm.tree.analysis.BasicValue;
10 | import org.objectweb.asm.tree.analysis.Frame;
11 |
12 | public class TableSwitchInsnHandler implements InsnHandler {
13 | @Override
14 | public void compileInsn(Context context, CompilerContext compilerContext) {
15 | Method method = compilerContext.compileTo();
16 | TableSwitchInsnNode instruction = compilerContext.instruction();
17 | Frame frame = compilerContext.frames()[compilerContext.instructions().indexOf(instruction)];
18 | int stackHeight = frame.getStackSize();
19 | SwitchCaseCodeSegment s = new SwitchCaseCodeSegment(Printable.formatted("stack[$l].i", stackHeight - 1));
20 | for (int i = instruction.min; i <= instruction.max; i++) {
21 | s.newCase("$l", i);
22 | s.addStmt("goto $l", compilerContext.labels().get(instruction.labels.get(i - instruction.min)));
23 | }
24 | s.dflt();
25 | s.addStmt("goto $l", compilerContext.labels().get(instruction.dflt));
26 | method.addP(s);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/TypeInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.cppwriter.Method;
6 | import me.x150.j2cc.util.Util;
7 | import org.objectweb.asm.tree.TypeInsnNode;
8 |
9 | public class TypeInsnNodeHandler implements InsnHandler {
10 | @Override
11 | public void compileInsn(Context context, CompilerContext compilerContext) {
12 | TypeInsnNode insn = compilerContext.instruction();
13 | int stack = compilerContext.frames()[compilerContext.instructions().indexOf(insn)].getStackSize();
14 | Method m = compilerContext.compileTo();
15 | switch (insn.getOpcode()) {
16 | case NEW -> {
17 | String desc = insn.desc;
18 | String className = compilerContext.cache().getOrCreateClassResolve(desc, 0);
19 | m.addStatement("stack[$l].l = env->AllocObject($l)", stack, className);
20 | }
21 | case CHECKCAST -> {
22 | String desc = insn.desc;
23 | String className = compilerContext.cache().getOrCreateClassResolve(desc, 0);
24 | m.beginScope("if (!env->IsInstanceOf(stack[$l].l, $l))", stack - 1, className);
25 | String orGenerateClassFind = compilerContext.cache().getOrCreateClassResolve("java/lang/ClassCastException", 0);
26 | m.addStatement("env->ThrowNew($l, $s)", orGenerateClassFind, "");
27 | compilerContext.exceptionCheck(true);
28 | m.endScope();
29 | }
30 | case ANEWARRAY -> {
31 | String orGenerateClassFind = compilerContext.cache().getOrCreateClassResolve(insn.desc, 0);
32 | m.addStatement("stack[$l].l = env->NewObjectArray(stack[$l].i, $l, nullptr)", stack - 1, stack - 1, orGenerateClassFind);
33 | }
34 | case INSTANCEOF -> {
35 | String real = compilerContext.cache().getOrCreateClassResolve(insn.desc, 0);
36 | m.addStatement("stack[$l].i = (stack[$l].l != nullptr && env->IsInstanceOf(stack[$l].l, $l)) ? 1 : 0", stack - 1, stack - 1, stack - 1, real);
37 | }
38 | default -> throw Util.unimplemented(insn.getOpcode());
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compiler/handler/VarInsnNodeHandler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compiler.handler;
2 |
3 | import me.x150.j2cc.compiler.CompilerContext;
4 | import me.x150.j2cc.compiler.DefaultCompiler;
5 | import me.x150.j2cc.conf.Context;
6 | import me.x150.j2cc.util.Util;
7 | import org.objectweb.asm.Type;
8 | import org.objectweb.asm.tree.VarInsnNode;
9 |
10 | import java.lang.reflect.Modifier;
11 |
12 | public class VarInsnNodeHandler implements InsnHandler {
13 | @Override
14 | public void compileInsn(Context context, CompilerContext compilerContext) {
15 |
16 | VarInsnNode insn = compilerContext.instruction();
17 | int stackNow = compilerContext.frames()[compilerContext.instructions().indexOf(insn)].getStackSize();
18 | Type[] argTypes = Type.getArgumentTypes(compilerContext.methodNode().desc);
19 | int baseArgSizes = (Type.getArgumentsAndReturnSizes(compilerContext.methodNode().desc) >> 2) - 1;
20 | Type[] mappedArgSlots = new Type[baseArgSizes];
21 | int idx = 0;
22 | for (Type argType : argTypes) {
23 | mappedArgSlots[idx] = argType;
24 | idx += argType.getSize();
25 | }
26 | int nrP = baseArgSizes + (Modifier.isStatic(compilerContext.methodNode().access) ? 0 : 1);
27 | boolean isParam = insn.var < nrP;
28 | switch (insn.getOpcode()) {
29 | case ILOAD, FLOAD, DLOAD, LLOAD, ALOAD -> {
30 | char c = "ijfdl".charAt(insn.getOpcode() - ILOAD);
31 | if (isParam) {
32 | compilerContext.compileTo().addStatement("stack[$l].$l = param$l", stackNow, c, insn.var);
33 | } else compilerContext.compileTo().addStatement("stack[$l].$l = locals[$l].$l", stackNow, c, insn.var, c);
34 | }
35 | case ISTORE, FSTORE, DSTORE, LSTORE, ASTORE -> {
36 | char c = "ijfdl".charAt(insn.getOpcode() - ISTORE);
37 | if (isParam) {
38 | int idx1 = insn.var - (Modifier.isStatic(compilerContext.methodNode().access) ? 0 : 1);
39 |
40 | Type argumentType = mappedArgSlots[idx1];
41 | String s = DefaultCompiler.jTypeMap.getOrDefault(argumentType, argumentType.getSort() == Type.ARRAY ? "jobjectArray" : "jobject");
42 | compilerContext.compileTo().addStatement("param$l = ($l) stack[$l].$l", insn.var, s, stackNow - 1, c);
43 | } else
44 | compilerContext.compileTo().addStatement("locals[$l].$l = stack[$l].$l", insn.var, c, stackNow - 1, c);
45 | }
46 | default -> throw Util.unimplemented(insn.getOpcode());
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compilerExec/Compiler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compilerExec;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Path;
5 |
6 | public interface Compiler {
7 | Process invoke(Path cwd, String targetTriple, String... args) throws IOException;
8 |
9 | boolean supportsCrossComp();
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compilerExec/GccCompiler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compilerExec;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Path;
5 |
6 | public class GccCompiler implements Compiler {
7 |
8 | private final Path execPath;
9 |
10 | public GccCompiler(Path execPath) {
11 | this.execPath = execPath;
12 | }
13 |
14 | @Override
15 | public Process invoke(Path cwd, String targetTriple, String... args) throws IOException {
16 | String[] full = new String[args.length + 1];
17 | full[0] = execPath.toAbsolutePath().toString();
18 | System.arraycopy(args, 0, full, 1, args.length);
19 |
20 | ProcessBuilder pb = new ProcessBuilder();
21 | pb.directory(cwd.toFile());
22 | pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
23 | pb.redirectError(ProcessBuilder.Redirect.INHERIT);
24 | pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
25 | pb.command(full);
26 | return pb.start();
27 | }
28 |
29 | @Override
30 | public boolean supportsCrossComp() {
31 | return false;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/compilerExec/ZigCompiler.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.compilerExec;
2 |
3 | import java.io.IOException;
4 | import java.nio.file.Files;
5 | import java.nio.file.Path;
6 | import java.util.Arrays;
7 | import java.util.List;
8 |
9 | //@Nativeify
10 | public class ZigCompiler implements Compiler {
11 | private static final String[] knownZigPaths = {
12 | "zig",
13 | "zig.exe"
14 | };
15 | private final Path target;
16 |
17 | public ZigCompiler(Path target) {
18 | this.target = target;
19 | }
20 |
21 |
22 | public static ZigCompiler locate(Path p) {
23 | List list = Arrays.stream(knownZigPaths).map(p::resolve).filter(Files::exists).toList();
24 | if (list.isEmpty()) throw new IllegalStateException("Could not locate zig compiler in " + p.toAbsolutePath());
25 | if (list.size() > 1) {
26 | StringBuilder s1 = new StringBuilder("Multiple zig binaries found:");
27 | for (Path path : list) {
28 | s1.append("\n ").append(path);
29 | }
30 | s1.append("\n").append("Looked in: ").append(p);
31 | throw new IllegalStateException(s1.toString());
32 | }
33 | return new ZigCompiler(list.getFirst());
34 | }
35 |
36 | @Override
37 | public boolean supportsCrossComp() {
38 | return true;
39 | }
40 |
41 | @Override
42 | public Process invoke(Path cwd, String targetTriple, String... args) throws IOException {
43 | String[] full = new String[args.length + 2 + (targetTriple == null ? 0 : 2)];
44 | full[0] = target.toAbsolutePath().toString();
45 | full[1] = "c++";
46 | System.arraycopy(args, 0, full, 2, args.length);
47 | if (targetTriple != null) {
48 | full[full.length - 2] = "-target";
49 | full[full.length - 1] = targetTriple;
50 | }
51 |
52 | ProcessBuilder pb = new ProcessBuilder();
53 | pb.directory(cwd.toFile());
54 | pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
55 | pb.redirectError(ProcessBuilder.Redirect.INHERIT);
56 | pb.redirectInput(ProcessBuilder.Redirect.INHERIT);
57 | pb.command(full);
58 | return pb.start();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/Context.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf;
2 |
3 | import j2cc.Exclude;
4 | import lombok.Builder;
5 | import lombok.NonNull;
6 | import lombok.Singular;
7 | import me.x150.j2cc.compiler.CompilerEngine;
8 | import me.x150.j2cc.compiler.DefaultCompiler;
9 | import me.x150.j2cc.input.InputProvider;
10 | import me.x150.j2cc.output.OutputSink;
11 | import me.x150.j2cc.tree.Workspace;
12 | import me.x150.j2cc.util.ClassFilter;
13 | import me.x150.j2cc.util.MemberFilter;
14 | import org.jetbrains.annotations.NotNull;
15 |
16 | import java.nio.file.Path;
17 | import java.util.List;
18 | import java.util.concurrent.ExecutorService;
19 | import java.util.concurrent.Executors;
20 |
21 | @Builder
22 | public record Context(
23 | @NonNull Workspace workspace,
24 | Path specialTempPath,
25 | @NotNull Path utilPath,
26 | boolean keepTemp,
27 | @NotNull InputProvider input,
28 | @NotNull OutputSink output,
29 | int pJobs,
30 | @Singular List customTargets,
31 | @Singular List zigArgs,
32 | @NotNull Configuration.DebugSettings debug,
33 | @NotNull ObfuscationSettings obfuscationSettings,
34 | @NotNull CompilerEngine compiler,
35 | boolean compileAllMethods,
36 |
37 | List> extraClassFilters,
38 | List> extraMemberFilters,
39 | List classesToInclude,
40 | List methodsToInclude,
41 |
42 | boolean skipOptimization,
43 |
44 | Path[] postCompileCommands
45 | ) {
46 |
47 | public record AnnotationOverrides(T[] filters, Exclude.From[] extraExcludeTypes) {
48 | }
49 |
50 | public record ObfuscationSettings(Configuration.RenamerSettings renamerSettings,
51 | boolean vagueExceptions,
52 | Configuration.AntiHookSettings antiHook) {
53 |
54 | }
55 |
56 | public ExecutorService parallelExecutorForNThreads() {
57 | if (pJobs <= 0) return Executors.newCachedThreadPool(); // unlimited
58 | if (pJobs == 1) return Executors.newSingleThreadExecutor(); // optimize for one thread
59 | return Executors.newFixedThreadPool(pJobs);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/javaconf/Configurable.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf.javaconf;
2 |
3 | import java.util.Deque;
4 | import java.util.Set;
5 |
6 | public interface Configurable {
7 | String[] getConfigKeys();
8 |
9 | Class> getConfigValueType(String key);
10 |
11 | Object getConfigValue(String key);
12 |
13 | void setConfigValue(String key, Object value);
14 |
15 | void validatePathsFilled(Deque currentPath, Set missing);
16 |
17 | String getDescription(String key);
18 |
19 | String getExample(String key);
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/javaconf/Util.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf.javaconf;
2 |
3 | import java.util.stream.Stream;
4 |
5 | public class Util {
6 | public static Stream> walkHierarchy(Class cl) {
7 | Stream.Builder> real = Stream.builder();
8 | Class super T> current = cl;
9 | while (current != null) {
10 | real.accept(current);
11 | current = current.getSuperclass();
12 | }
13 | return real.build();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/javaconf/annots/ConfigValue.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf.javaconf.annots;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.FIELD)
10 | public @interface ConfigValue {
11 | String value();
12 |
13 | String description() default "";
14 |
15 | String exampleContent() default "";
16 |
17 | boolean required() default false;
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/javaconf/annots/DTOConfigurable.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf.javaconf.annots;
2 |
3 | import me.x150.j2cc.conf.javaconf.Configurable;
4 | import me.x150.j2cc.conf.javaconf.Util;
5 |
6 | import java.lang.invoke.MethodHandles;
7 | import java.lang.invoke.VarHandle;
8 | import java.lang.reflect.Array;
9 | import java.lang.reflect.Field;
10 | import java.lang.reflect.Modifier;
11 | import java.util.*;
12 | import java.util.stream.Collectors;
13 |
14 | public abstract class DTOConfigurable implements Configurable {
15 |
16 | record E(ConfigValue cv, Field target) {
17 |
18 | }
19 |
20 | private final Map confEntries;
21 | private final Map confMeta;
22 | private final String[] keys;
23 | private final BitSet setKeys;
24 |
25 | public DTOConfigurable() {
26 | MethodHandles.Lookup lookup = MethodHandles.lookup();
27 | List list = discoverConfigValueFields(getClass());
28 | List list1 = Util.walkHierarchy(getClass())
29 | .flatMap(it -> Arrays.stream(it.getDeclaredFields()))
30 | .filter(f -> f.isAnnotationPresent(ConfigValue.class))
31 | .filter(f -> !Modifier.isPublic(f.getModifiers()))
32 | .map(Field::toString).toList();
33 | if (!list1.isEmpty()) {
34 | throw new IllegalStateException("Fields have @ConfigValue but aren't public: " + list1);
35 | }
36 | confEntries = list.stream()
37 | .collect(Collectors.toMap(e -> e.cv.value(), e -> {
38 | try {
39 | return lookup.unreflectVarHandle(e.target);
40 | } catch (IllegalAccessException ex) {
41 | throw new RuntimeException(ex);
42 | }
43 | }));
44 | this.confMeta = list.stream().collect(Collectors.toMap(e -> e.cv.value(), e -> e.cv));
45 | this.keys = confEntries.keySet().toArray(String[]::new);
46 | Arrays.sort(this.keys);
47 | this.setKeys = new BitSet(keys.length);
48 | }
49 |
50 | private static List discoverConfigValueFields(Class> cl) {
51 | List fld = new ArrayList<>();
52 | for (Field field : cl.getFields()) {
53 | ConfigValue cfv = field.getAnnotation(ConfigValue.class);
54 | if (cfv == null) continue;
55 | if (Modifier.isStatic(field.getModifiers()))
56 | throw new IllegalStateException("Static field " + field + " has @" + ConfigValue.class.getSimpleName());
57 | fld.add(new E(cfv, field));
58 | }
59 | return fld;
60 | }
61 |
62 | @Override
63 | public String[] getConfigKeys() {
64 | return keys;
65 | }
66 |
67 | @Override
68 | public Class> getConfigValueType(String key) {
69 | return confEntries.get(key).varType();
70 | }
71 |
72 | @Override
73 | public Object getConfigValue(String key) {
74 | return confEntries.get(key).get(this);
75 | }
76 |
77 | @Override
78 | public void setConfigValue(String key, Object value) {
79 | confEntries.get(key).set(this, value);
80 | setKeys.set(Arrays.binarySearch(this.keys, key), value != null);
81 | }
82 |
83 | private void validateObject(Deque currentPath, Set missing, Object val) {
84 | if (val instanceof Configurable cf) cf.validatePathsFilled(currentPath, missing);
85 | else if (val != null && val.getClass().isArray()) {
86 | int len = Array.getLength(val);
87 | for (int i1 = 0; i1 < len; i1++) {
88 | Object o = Array.get(val, i1);
89 | currentPath.add(String.valueOf(i1));
90 | validateObject(currentPath, missing, o);
91 | currentPath.removeLast();
92 | }
93 | }
94 | }
95 |
96 | @Override
97 | public void validatePathsFilled(Deque currentPath, Set missing) {
98 | for (int i = 0; i < keys.length; i++) {
99 | String key = keys[i];
100 | currentPath.add(key);
101 | // validate the value itself (if not null)
102 | validateObject(currentPath, missing, getConfigValue(key));
103 | if (!setKeys.get(i) && confMeta.get(key).required()) {
104 | // we have a missing key
105 | missing.add(String.join(".", currentPath));
106 | }
107 | currentPath.removeLast();
108 | }
109 | }
110 |
111 | @Override
112 | public String getDescription(String key) {
113 | return confMeta.get(key).description();
114 | }
115 |
116 | @Override
117 | public String getExample(String key) {
118 | return confMeta.get(key).exampleContent();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/conf/javaconf/annots/MapConfigurable.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.conf.javaconf.annots;
2 |
3 | import me.x150.j2cc.conf.javaconf.Configurable;
4 |
5 | import java.util.Deque;
6 | import java.util.Map;
7 | import java.util.Set;
8 |
9 | public class MapConfigurable implements Configurable {
10 |
11 | private final Map children;
12 | private final String[] array;
13 |
14 | public MapConfigurable(Map children) {
15 | this.children = children;
16 | array = children.keySet().toArray(new String[0]);
17 | }
18 |
19 | @Override
20 | public String[] getConfigKeys() {
21 | return array;
22 | }
23 |
24 | @Override
25 | public Class> getConfigValueType(String key) {
26 | return children.get(key).getClass();
27 | }
28 |
29 | @Override
30 | public Object getConfigValue(String key) {
31 | return children.get(key);
32 | }
33 |
34 | @Override
35 | public void setConfigValue(String key, Object value) {
36 | Class> type = getConfigValueType(key);
37 | if (!type.isInstance(value)) throw new IllegalStateException(type + " not assignable from " + value.getClass());
38 | children.put(key, (Configurable) value);
39 | }
40 |
41 | @Override
42 | public void validatePathsFilled(Deque s, Set missing) {
43 | for (Map.Entry stringConfigurableEntry : children.entrySet()) {
44 | s.add(stringConfigurableEntry.getKey());
45 | stringConfigurableEntry.getValue().validatePathsFilled(s, missing);
46 | s.removeLast();
47 | }
48 | }
49 |
50 | @Override
51 | public String getDescription(String key) {
52 | return null;
53 | }
54 |
55 | @Override
56 | public String getExample(String key) {
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/cppwriter/Include.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.cppwriter;
2 |
3 | public class Include implements Printable {
4 |
5 | private final String headerName;
6 | private final boolean local;
7 |
8 | public Include(String headerName, boolean local) {
9 | this.headerName = headerName;
10 | this.local = local;
11 | }
12 |
13 | @Override
14 | public String stringify() {
15 | return "#include " + (local ? "\"" : "<") + headerName + (local ? "\"" : ">");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/cppwriter/Printable.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.cppwriter;
2 |
3 | import me.x150.j2cc.util.Util;
4 |
5 | public interface Printable {
6 | static Printable constant(String s) {
7 | return () -> s;
8 | }
9 |
10 | static Printable formatted(String s, Object... o) {
11 | return () -> Util.fmt(s, o);
12 | }
13 |
14 | String stringify();
15 | }
16 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/cppwriter/SourceBuilder.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.cppwriter;
2 |
3 | import java.util.List;
4 | import java.util.concurrent.CopyOnWriteArrayList;
5 |
6 | public class SourceBuilder implements Printable {
7 | private final List members = new CopyOnWriteArrayList<>();
8 | private final List top = new CopyOnWriteArrayList<>();
9 |
10 | // @Nativeify
11 | public Method method(String flags, String ret, String name, String... params) {
12 | Method e = new Method(flags, ret, name, params);
13 | members.add(e);
14 | return e;
15 | }
16 |
17 | // @Nativeify
18 | public void include(String name, boolean local) {
19 | top.add(new Include(name, local));
20 | }
21 |
22 | // @Nativeify
23 | public void global(String p, Object... params) {
24 | top.add(Printable.formatted(p, params));
25 | }
26 |
27 | public void addTop(Printable p) {
28 | top.add(p);
29 | }
30 |
31 | @Override
32 | public String stringify() {
33 | StringBuilder sb = new StringBuilder();
34 | for (Printable printable : top) {
35 | sb.append(printable.stringify()).append("\n");
36 | }
37 | for (Printable member : members) {
38 | sb.append(member.stringify()).append("\n");
39 | }
40 | sb.deleteCharAt(sb.length()-1);
41 | return sb.toString();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/cppwriter/SwitchCaseCodeSegment.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.cppwriter;
2 |
3 | import me.x150.j2cc.util.Util;
4 |
5 | import java.util.ArrayList;
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.stream.Collectors;
9 |
10 | public class SwitchCaseCodeSegment implements Printable {
11 | private final Printable what;
12 | private final List cases;
13 |
14 | public SwitchCaseCodeSegment(Printable what) {
15 | this.what = what;
16 | cases = new ArrayList<>();
17 | }
18 |
19 | public void newCase(String w, Object... o) {
20 | cases.add(new CaseElement(Printable.constant(Util.fmt(w, o)), new ArrayList<>(), false));
21 | }
22 |
23 | public void add(String w, Object... o) {
24 | cases.getLast().body.add(Printable.constant(Util.fmt(w, o)));
25 | }
26 |
27 | public void addStmt(String w, Object... o) {
28 | add(w + ";", o);
29 | }
30 |
31 | public void dflt() {
32 | cases.add(new CaseElement(null, new ArrayList<>(), true));
33 | }
34 |
35 | @Override
36 | public String stringify() {
37 | StringBuilder sb = new StringBuilder();
38 | sb.append("switch (").append(what.stringify()).append(") {\n");
39 | for (CaseElement aCase : cases) {
40 | if (aCase.dflt) sb.append(" default:\n");
41 | else sb.append(" case ").append(aCase.what.stringify()).append(":\n");
42 | sb.append(" ").append(aCase.body.stream().map(Printable::stringify).flatMap(s -> Arrays.stream(s.split("\n"))).collect(Collectors.joining("\n "))).append("\n");
43 | }
44 | sb.append("}");
45 | return sb.toString();
46 | }
47 |
48 | record CaseElement(Printable what, List body, boolean dflt) {
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/exc/CompilationFailure.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.exc;
2 |
3 | import lombok.experimental.StandardException;
4 |
5 | @StandardException
6 | public class CompilationFailure extends RuntimeException {
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/input/DirectoryInputProvider.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.input;
2 |
3 | import me.x150.j2cc.tree.resolver.DirectoryResolver;
4 | import me.x150.j2cc.tree.resolver.Resolver;
5 |
6 | import java.nio.file.Path;
7 |
8 | public class DirectoryInputProvider implements InputProvider {
9 |
10 | private final Path rootPath;
11 |
12 | public DirectoryInputProvider(Path rootPath) {
13 | this.rootPath = rootPath;
14 | }
15 |
16 | @Override
17 | public Path getFile(String path) {
18 | return rootPath.resolve(path);
19 | }
20 |
21 | @Override
22 | public Resolver toResolver() {
23 | return new DirectoryResolver(rootPath);
24 | }
25 |
26 | @Override
27 | public void close() {
28 | // noop
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return String.format(
34 | "%s{rootPath=%s}", getClass().getSimpleName(), this.rootPath);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/input/InputProvider.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.input;
2 |
3 | import me.x150.j2cc.tree.resolver.Resolver;
4 |
5 | import java.nio.file.Path;
6 |
7 | public interface InputProvider extends AutoCloseable {
8 | Path getFile(String path);
9 |
10 | Resolver toResolver();
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/input/JarInputProvider.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.input;
2 |
3 | import me.x150.j2cc.tree.resolver.FsResolver;
4 | import me.x150.j2cc.tree.resolver.Resolver;
5 |
6 | import java.nio.file.FileSystem;
7 | import java.nio.file.Path;
8 |
9 | public record JarInputProvider(FileSystem fs) implements InputProvider {
10 | @Override
11 | public Path getFile(String path) {
12 | return fs.getPath(path);
13 | }
14 |
15 | @Override
16 | public Resolver toResolver() {
17 | return new FsResolver(fs);
18 | }
19 |
20 | @Override
21 | public void close() {
22 |
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return String.format(
28 | "%s{fs=%s}", getClass().getSimpleName(), this.fs);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/ObfuscationContext.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator;
2 |
3 | import me.x150.j2cc.tree.Remapper;
4 |
5 | public record ObfuscationContext(String mainClassName, Remapper mapper
6 | ) {
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/Obfuscator.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator;
2 |
3 | import j2cc.Exclude;
4 | import lombok.extern.log4j.Log4j2;
5 | import me.x150.j2cc.J2CC;
6 | import me.x150.j2cc.conf.Context;
7 | import me.x150.j2cc.obfuscator.etc.*;
8 | import me.x150.j2cc.obfuscator.optim.*;
9 | import me.x150.j2cc.obfuscator.refs.MhCallRef;
10 | import me.x150.j2cc.obfuscator.strings.StringObfuscator;
11 | import me.x150.j2cc.tree.Workspace;
12 | import me.x150.j2cc.util.Util;
13 | import org.objectweb.asm.tree.ClassNode;
14 | import org.objectweb.asm.tree.FieldNode;
15 | import org.objectweb.asm.tree.MethodNode;
16 |
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.Collection;
20 | import java.util.List;
21 | import java.util.jar.Manifest;
22 |
23 | @Log4j2
24 | public class Obfuscator {
25 | public final ObfuscatorPass[] passes = {
26 | new RemoveDebugInfo(),
27 | new OptimizerPass(),
28 | new ExtractConstructors(),
29 | new Inliner(),
30 | // new MethodSplitter(),
31 | // new FlowFlatten(),
32 | // new MethodCombiner(),
33 | // new FlowExc(),
34 | // new FlowReorder(),
35 | // new ClassSplitter(),
36 | // new SplitTrapRanges(),
37 | new MhCallRef(),
38 | // new StringObfuscator(),
39 | // new ConstantObfuscator(),
40 | new PushOptimizer(),
41 | new BranchInliner(),
42 | new EliminateDeadCode()
43 | // new Real()
44 | };
45 |
46 | public static boolean skip(Workspace wsp, Context context, ClassNode cn) {
47 | String name = cn.name;
48 | while (name.contains("/")) {
49 | name = name.substring(0, name.lastIndexOf('/'));
50 | Workspace.ClassInfo pInf = wsp.get(name + "/package-info");
51 | if (pInf != null) {
52 | if (Util.shouldIgnore(context, pInf.node(), Exclude.From.OBFUSCATION)) return true;
53 | }
54 | }
55 | return skip(context, cn);
56 | }
57 |
58 | public static boolean skip(Context context, ClassNode cn) {
59 | return Util.shouldIgnore(context, cn, Exclude.From.OBFUSCATION);
60 | }
61 |
62 | public static boolean skip(Context context, String owner, MethodNode mn) {
63 | return Util.shouldIgnore(context, owner, mn, Exclude.From.OBFUSCATION);
64 | }
65 |
66 | public static boolean skip(Context context, String name, FieldNode fn) {
67 | return Util.shouldIgnore(context, name, fn, Exclude.From.OBFUSCATION);
68 | }
69 |
70 | public List obfuscate(ObfuscationContext ctx, Context context, List classes) {
71 | List theEntries = new ArrayList<>(classes);
72 | List ci = new ArrayList<>();
73 | for (ObfuscatorPass pass : passes) {
74 | if (pass.shouldRun()) {
75 | log.debug("Running pass {}", pass.getClass().getSimpleName());
76 | pass.obfuscate(ctx, context, theEntries);
77 | Collection additionalClasses = pass.getAdditionalClasses();
78 | Collection newClasses = context.workspace().registerAndMapExternal(additionalClasses);
79 | ci.addAll(newClasses);
80 | theEntries.addAll(newClasses.stream().map(it -> new J2CC.ClassEntry(it, "")).toList());
81 | }
82 | }
83 | return ci;
84 | }
85 |
86 | public void ensureConfValid() {
87 | for (ObfuscatorPass pass : passes) {
88 | Collection> requires = pass.requires();
89 | if (!pass.shouldRun()) continue;
90 | if (requires == null) continue;
91 | for (Class extends ObfuscatorPass> require : requires) {
92 | ObfuscatorPass obfuscatorPass = Arrays.stream(passes).filter(f -> f.getClass() == require).findFirst().orElseThrow();
93 | if (!obfuscatorPass.shouldRun())
94 | throw new IllegalStateException("Transformer " + pass.getClass().getSimpleName() + " requires pass " + require.getSimpleName() + " to be enabled, but it isn't.");
95 | }
96 | }
97 | }
98 |
99 | public void transformManifest(Context ctx, Manifest mf) {
100 | for (ObfuscatorPass pass : passes) {
101 | if (pass.shouldRun()) {
102 | pass.modifyManifest(ctx, mf);
103 | }
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/ObfuscatorPass.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator;
2 |
3 | import me.x150.j2cc.J2CC;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.conf.javaconf.annots.ConfigValue;
6 | import me.x150.j2cc.conf.javaconf.annots.DTOConfigurable;
7 | import org.objectweb.asm.tree.ClassNode;
8 |
9 | import java.util.Collection;
10 | import java.util.Collections;
11 | import java.util.jar.Manifest;
12 |
13 | public abstract class ObfuscatorPass extends DTOConfigurable {
14 | @ConfigValue(value = "enabled", description = "Enable this transformer")
15 | public boolean enabled = false;
16 |
17 |
18 | // public abstract void copyConfiguration(ObfuscationSettings obfuscationSettings);
19 |
20 | public abstract void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes);
21 |
22 | public void modifyManifest(Context ctx, Manifest manifest) {
23 | }
24 |
25 | public boolean shouldRun() {
26 | return enabled;
27 | }
28 |
29 | public boolean hasConfiguration() {
30 | return true;
31 | }
32 |
33 | public Collection getAdditionalClasses() {
34 | return Collections.emptyList();
35 | }
36 |
37 | public Collection> requires() {
38 | return Collections.emptyList();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/etc/ExtractConstructors.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.etc;
2 |
3 | import lombok.SneakyThrows;
4 | import me.x150.j2cc.J2CC;
5 | import me.x150.j2cc.conf.Context;
6 | import me.x150.j2cc.obfuscator.ObfuscationContext;
7 | import me.x150.j2cc.obfuscator.Obfuscator;
8 | import me.x150.j2cc.obfuscator.ObfuscatorPass;
9 | import org.apache.logging.log4j.LogManager;
10 | import org.apache.logging.log4j.Logger;
11 | import org.objectweb.asm.Opcodes;
12 | import org.objectweb.asm.tree.ClassNode;
13 | import org.objectweb.asm.tree.FieldNode;
14 | import org.objectweb.asm.tree.MethodNode;
15 |
16 | import java.lang.reflect.Modifier;
17 | import java.util.ArrayList;
18 | import java.util.Collection;
19 | import java.util.HexFormat;
20 | import java.util.List;
21 | import java.util.concurrent.ThreadLocalRandom;
22 |
23 | public class ExtractConstructors extends ObfuscatorPass {
24 | private static final HexFormat hf = HexFormat.ofDelimiter("").withLowerCase();
25 | private static final Logger log = LogManager.getLogger(ExtractConstructors.class);
26 |
27 | @SneakyThrows
28 | @Override
29 | public void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes) {
30 | byte[] mName = new byte[16];
31 | for (J2CC.ClassEntry aClass : classes) {
32 | ClassNode node = aClass.info().node();
33 | if (Obfuscator.skip(context, node)) continue;
34 | if (Modifier.isInterface(node.access) && node.fields.stream().anyMatch(it -> Modifier.isFinal(it.access))) {
35 | log.warn("Can't extract constructors from {}: Is interface, and final fields are present (cannot remove final modifier from fields in interface)", node.name);
36 | continue;
37 | }
38 | List newMethods = new ArrayList<>();
39 | for (MethodNode method : node.methods) {
40 | if (!method.name.startsWith("<") || Obfuscator.skip(context, node.name, method)) continue;
41 | if (method.name.equals("")) {
42 | // we cant do s
43 | continue;
44 | }
45 | ThreadLocalRandom.current().nextBytes(mName);
46 | // found a clinit
47 | String extractedName = "_" + hf.formatHex(mName);
48 |
49 | MethodNode newConstructor = new MethodNode(method.access, method.name, method.desc, method.signature, method.exceptions.toArray(String[]::new));
50 | method.access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
51 | method.name = extractedName;
52 | method.desc = "()V";
53 | method.signature = null;
54 | method.exceptions = null;
55 | newConstructor.visitMethodInsn(Opcodes.INVOKESTATIC, node.name, method.name, "()V", false);
56 | newConstructor.visitInsn(Opcodes.RETURN);
57 | newMethods.add(newConstructor);
58 | }
59 | node.methods.addAll(newMethods);
60 | if (!newMethods.isEmpty()) {
61 | // make any static final fields non-final, since they're now being accessed from an external method
62 | for (FieldNode field : node.fields) {
63 | if (Modifier.isStatic(field.access)) field.access = field.access & ~Modifier.FINAL;
64 | }
65 | }
66 | }
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/etc/RemoveDebugInfo.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.etc;
2 |
3 | import me.x150.j2cc.J2CC;
4 | import me.x150.j2cc.conf.Context;
5 | import me.x150.j2cc.obfuscator.ObfuscationContext;
6 | import me.x150.j2cc.obfuscator.Obfuscator;
7 | import me.x150.j2cc.obfuscator.ObfuscatorPass;
8 | import org.objectweb.asm.tree.*;
9 |
10 | import java.util.Collection;
11 | import java.util.List;
12 | import java.util.Optional;
13 |
14 | public class RemoveDebugInfo extends ObfuscatorPass {
15 | @Override
16 | public void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes) {
17 | for (J2CC.ClassEntry aClass : classes) {
18 | ClassNode node = aClass.info().node();
19 | if (Obfuscator.skip(context.workspace(), context, node)) continue;
20 | for (MethodNode method : node.methods) {
21 | for (AbstractInsnNode instruction : method.instructions) {
22 | if (instruction instanceof LineNumberNode) method.instructions.remove(instruction);
23 | }
24 | Optional.ofNullable(method.localVariables).ifPresent(List::clear);
25 | Optional.ofNullable(method.visibleLocalVariableAnnotations).ifPresent(List::clear);
26 | Optional.ofNullable(method.invisibleLocalVariableAnnotations).ifPresent(List::clear);
27 | method.signature = null;
28 | }
29 | for (FieldNode field : node.fields) {
30 | field.signature = null;
31 | }
32 | node.signature = null;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/optim/EliminateDeadCode.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.optim;
2 |
3 | import lombok.SneakyThrows;
4 | import lombok.extern.log4j.Log4j2;
5 | import me.x150.j2cc.J2CC;
6 | import me.x150.j2cc.conf.Context;
7 | import me.x150.j2cc.obfuscator.ObfuscationContext;
8 | import me.x150.j2cc.obfuscator.ObfuscatorPass;
9 | import org.objectweb.asm.tree.AbstractInsnNode;
10 | import org.objectweb.asm.tree.ClassNode;
11 | import org.objectweb.asm.tree.LabelNode;
12 | import org.objectweb.asm.tree.MethodNode;
13 | import org.objectweb.asm.tree.analysis.Analyzer;
14 | import org.objectweb.asm.tree.analysis.BasicInterpreter;
15 | import org.objectweb.asm.tree.analysis.BasicValue;
16 | import org.objectweb.asm.tree.analysis.Frame;
17 |
18 | import java.util.Collection;
19 |
20 | @Log4j2
21 | public class EliminateDeadCode extends ObfuscatorPass {
22 | @Override
23 | public boolean shouldRun() {
24 | return true;
25 | }
26 |
27 | @Override
28 | public boolean hasConfiguration() {
29 | return false;
30 | }
31 |
32 | @Override
33 | @SneakyThrows
34 | public void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes) {
35 | Analyzer bv = new Analyzer<>(new BasicInterpreter());
36 | for (J2CC.ClassEntry aClass : classes) {
37 | ClassNode node = aClass.info().node();
38 | for (MethodNode method : node.methods) {
39 | Frame[] analyze = bv.analyzeAndComputeMaxs(node.name, method);
40 | int offset = 0;
41 | for (AbstractInsnNode instruction : method.instructions) {
42 | int index = method.instructions.indexOf(instruction);
43 | if (instruction instanceof LabelNode) continue;
44 | if (analyze[index+offset] == null) {
45 | offset++;
46 | method.instructions.remove(instruction);
47 | }
48 | }
49 | if (offset > 0) log.info("[{}.{}] Removed {} dead instructions", node.name, method.name, offset);
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/optim/OptimizerPass.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.optim;
2 |
3 | import lombok.SneakyThrows;
4 | import lombok.extern.log4j.Log4j2;
5 | import me.x150.j2cc.J2CC;
6 | import me.x150.j2cc.conf.Context;
7 | import me.x150.j2cc.obfuscator.ObfuscationContext;
8 | import me.x150.j2cc.obfuscator.Obfuscator;
9 | import me.x150.j2cc.obfuscator.ObfuscatorPass;
10 | import me.x150.j2cc.optimizer.*;
11 | import me.x150.j2cc.util.InvalidCodeGuard;
12 | import org.objectweb.asm.Opcodes;
13 | import org.objectweb.asm.tree.ClassNode;
14 | import org.objectweb.asm.tree.MethodNode;
15 |
16 | import java.util.Collection;
17 |
18 | @Log4j2
19 | public class OptimizerPass extends ObfuscatorPass implements Opcodes {
20 | private static final Pass[] optimizerPasses = new Pass[]{
21 | new RemoveRedundantLabelsPass(),
22 | new ValueInliner(),
23 | new LocalPropagator(),
24 | new RemoveUnusedVars(),
25 | new PopInliner()
26 | };
27 |
28 | @SneakyThrows
29 | @Override
30 | public void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes) {
31 | if (context.skipOptimization()) {
32 | log.info("Skipping all optimization steps");
33 | return;
34 | }
35 | InvalidCodeGuard i = new InvalidCodeGuard();
36 | long s = classes.stream().mapToLong(it -> it.info().node().methods.size()).sum();
37 | for (J2CC.ClassEntry aClass : classes) {
38 | ClassNode node = aClass.info().node();
39 | if (Obfuscator.skip(context.workspace(), context, node)) continue;
40 | i.init(node);
41 | for (MethodNode method : node.methods) {
42 | if (Obfuscator.skip(context, node.name, method)) continue;
43 | long n = s--;
44 | if (n % 1000 == 0) log.debug("Optimizer: {} methods remaining", n);
45 | for (Pass optimizerPass : optimizerPasses) {
46 | optimizerPass.optimize(node, method, context.workspace());
47 | }
48 | }
49 | i.checkAndRestoreIfNeeded(node, true);
50 | }
51 | }
52 |
53 | @Override
54 | public boolean hasConfiguration() {
55 | return false;
56 | }
57 |
58 | @Override
59 | public boolean shouldRun() {
60 | return true;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/optim/PushOptimizer.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.optim;
2 |
3 | import lombok.extern.log4j.Log4j2;
4 | import me.x150.j2cc.J2CC;
5 | import me.x150.j2cc.conf.Context;
6 | import me.x150.j2cc.obfuscator.ObfuscationContext;
7 | import me.x150.j2cc.obfuscator.Obfuscator;
8 | import me.x150.j2cc.obfuscator.ObfuscatorPass;
9 | import org.objectweb.asm.Opcodes;
10 | import org.objectweb.asm.tree.*;
11 |
12 | import java.util.Collection;
13 |
14 | @Log4j2
15 | public class PushOptimizer extends ObfuscatorPass {
16 | @Override
17 | public void obfuscate(ObfuscationContext obfCtx, Context context, Collection classes) {
18 | long optimized = 0;
19 | for (J2CC.ClassEntry aClass : classes) {
20 | ClassNode no = aClass.info().node();
21 | if (Obfuscator.skip(context.workspace(), context, no)) continue;
22 | for (MethodNode method : no.methods) {
23 | if (Obfuscator.skip(context, no.name, method)) continue;
24 | boolean did;
25 | do {
26 | did = false;
27 | for (AbstractInsnNode abstractInsnNode : method.instructions) {
28 | AbstractInsnNode repl = switch (abstractInsnNode) {
29 | case IntInsnNode ii when ii.getOpcode() == Opcodes.SIPUSH && ii.operand >= Byte.MIN_VALUE && ii.operand <= Byte.MAX_VALUE ->
30 | new IntInsnNode(Opcodes.BIPUSH, ii.operand);
31 | case IntInsnNode ii when ii.getOpcode() == Opcodes.BIPUSH && ii.operand <= 5 && ii.operand >= -1 ->
32 | new InsnNode(Opcodes.ICONST_0 + ii.operand);
33 | case LdcInsnNode ld when ld.cst instanceof Integer i && i >= Short.MIN_VALUE && i <= Short.MAX_VALUE ->
34 | new IntInsnNode(Opcodes.SIPUSH, i);
35 | case LdcInsnNode ld when ld.cst instanceof Long l && (l == 0 || l == 1) ->
36 | new InsnNode((int) (Opcodes.LCONST_0 + l));
37 | case LdcInsnNode ld when ld.cst instanceof Float f && (f == 0 || f == 1) ->
38 | new InsnNode(Opcodes.FCONST_0 + f.intValue());
39 | case LdcInsnNode ld when ld.cst instanceof Double d && (d == 0 || d == 1) ->
40 | new InsnNode(Opcodes.DCONST_0 + d.intValue());
41 | default -> null;
42 | };
43 | if (repl != null) {
44 | did = true;
45 | method.instructions.set(abstractInsnNode, repl);
46 | optimized++;
47 | }
48 | }
49 | } while (did);
50 | }
51 | }
52 | log.info("Optimized {} int pushes", optimized);
53 | }
54 |
55 | @Override
56 | public boolean shouldRun() {
57 | return true;
58 | }
59 |
60 | @Override
61 | public boolean hasConfiguration() {
62 | return false;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/obfuscator/strings/StringDecoder.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.obfuscator.strings;
2 |
3 | import j2cc.Nativeify;
4 |
5 | import java.lang.invoke.MethodHandles;
6 |
7 | public final class StringDecoder {
8 |
9 | @Nativeify
10 | public static String idx(MethodHandles.Lookup lk, String e, Class> sc, String s, long l) {
11 | if (sc != String.class) throw new IllegalArgumentException();
12 | int a = (int) (l >> 32);
13 | int b = (int) (l & Integer.MAX_VALUE);
14 | char[] c = s.toCharArray();
15 | for (int i = 0; i < c.length; i++) {
16 | c[i] = (char) (c[i] ^ i * a ^ b);
17 | }
18 | return new String(c);
19 | }
20 |
21 | @Nativeify
22 | public static String key(MethodHandles.Lookup lk, String e, Class> sc, String s, String k) {
23 | if (sc != String.class) throw new IllegalArgumentException();
24 | char[] key = k.toCharArray();
25 | char[] data = s.toCharArray();
26 |
27 | for (int i = 0; i < data.length; i++) {
28 | data[i] = (char) (key[i] ^ data[i]);
29 | }
30 |
31 | return new String(data);
32 | }
33 | }
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/optimizer/LocalPropagator.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.optimizer;
2 |
3 | import lombok.extern.log4j.Log4j2;
4 | import me.x150.j2cc.tree.Workspace;
5 | import me.x150.j2cc.util.Util;
6 | import me.x150.j2cc.util.simulation.SimulatorInterpreter;
7 | import me.x150.j2cc.util.simulation.SimulatorValue;
8 | import org.objectweb.asm.Opcodes;
9 | import org.objectweb.asm.Type;
10 | import org.objectweb.asm.tree.*;
11 | import org.objectweb.asm.tree.analysis.Analyzer;
12 | import org.objectweb.asm.tree.analysis.AnalyzerException;
13 | import org.objectweb.asm.tree.analysis.Frame;
14 |
15 | import java.lang.reflect.Modifier;
16 | import java.util.HashMap;
17 | import java.util.Map;
18 |
19 | @Log4j2
20 | public class LocalPropagator implements Pass {
21 |
22 |
23 | @Override
24 | public void optimize(ClassNode owner, MethodNode method, Workspace wsp) throws AnalyzerException {
25 | boolean isStatic = Modifier.isStatic(method.access);
26 |
27 | Frame[] frames;
28 |
29 | try {
30 | frames = new Analyzer<>(new SimulatorInterpreter(wsp, false)).analyzeAndComputeMaxs(owner.name, method);
31 | } catch (AnalyzerException ase) {
32 | log.debug("Failed to analyse instruction {}: ", Util.stringifyInstruction(ase.node, Map.of()), ase);
33 | throw ase;
34 | }
35 |
36 | Map replacements = new HashMap<>();
37 |
38 | InsnList instructions = method.instructions;
39 | for (AbstractInsnNode instruction : instructions) {
40 | int i = instructions.indexOf(instruction);
41 |
42 | Frame frame = frames[i];
43 | if (frame == null) continue;
44 |
45 | if (instruction instanceof VarInsnNode vi && vi.getOpcode() >= Opcodes.ILOAD && vi.getOpcode() <= Opcodes.ALOAD) {
46 | int v = vi.var;
47 | if (v == 0 && !isStatic) {
48 | continue;
49 | }
50 |
51 | SimulatorValue theLocal = frame.getLocal(v);
52 |
53 | if (!theLocal.valueKnown() || theLocal.type().getSort() >= Type.ARRAY) continue;
54 | replacements.put(vi, theLocal);
55 | }
56 | }
57 | replacements.forEach((varInsnNode, ldcInsnNode) -> {
58 | Object cst = ldcInsnNode.value();
59 | AbstractInsnNode producer = switch (cst) {
60 | case null -> new InsnNode(Opcodes.ACONST_NULL);
61 | case Integer i when i >= -1 && i <= 5 -> new InsnNode(Opcodes.ICONST_0 + i);
62 | default -> new LdcInsnNode(cst);
63 | };
64 | method.instructions.insertBefore(varInsnNode, producer);
65 | method.instructions.remove(varInsnNode);
66 | });
67 | if (!replacements.isEmpty())
68 | log.info("[{}] Optimized {} local loads", owner.name, replacements.size());
69 |
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/optimizer/Pass.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.optimizer;
2 |
3 | import me.x150.j2cc.tree.Workspace;
4 | import org.objectweb.asm.tree.ClassNode;
5 | import org.objectweb.asm.tree.MethodNode;
6 | import org.objectweb.asm.tree.analysis.AnalyzerException;
7 |
8 | public interface Pass {
9 | void optimize(ClassNode owner, MethodNode method, Workspace wsp) throws AnalyzerException;
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/optimizer/RemoveRedundantLabelsPass.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.optimizer;
2 |
3 | import me.x150.j2cc.tree.Workspace;
4 | import org.objectweb.asm.tree.*;
5 |
6 | import java.util.stream.StreamSupport;
7 |
8 | public class RemoveRedundantLabelsPass implements Pass {
9 | @Override
10 | public void optimize(ClassNode owner, MethodNode methodNode, Workspace wsp) {
11 | for (AbstractInsnNode instruction : methodNode.instructions) {
12 | if (instruction instanceof LabelNode ln) {
13 | if (StreamSupport.stream(methodNode.instructions.spliterator(), true)
14 | .noneMatch(s -> {
15 | if (s instanceof JumpInsnNode ji && ji.label == ln) return true;
16 | if (s instanceof TableSwitchInsnNode ts && (ts.dflt == ln || ts.labels.contains(ln)))
17 | return true;
18 | if (s instanceof LineNumberNode lnn && lnn.start == ln) return true;
19 | return s instanceof LookupSwitchInsnNode ls && (ls.dflt == ln || ls.labels.contains(ln));
20 | })
21 | && (methodNode.tryCatchBlocks == null || methodNode.tryCatchBlocks.stream().noneMatch(s -> s.start == ln || s.end == ln || s.handler == ln))
22 | && (methodNode.localVariables == null || methodNode.localVariables.stream().noneMatch(it -> it.start == ln || it.end == ln))) {
23 | // redundant label
24 | methodNode.instructions.remove(ln);
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/optimizer/RemoveUnusedVars.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.optimizer;
2 |
3 | import lombok.SneakyThrows;
4 | import me.x150.j2cc.tree.Workspace;
5 | import org.objectweb.asm.Opcodes;
6 | import org.objectweb.asm.tree.*;
7 | import org.objectweb.asm.tree.analysis.Analyzer;
8 | import org.objectweb.asm.tree.analysis.BasicInterpreter;
9 | import org.objectweb.asm.tree.analysis.BasicValue;
10 | import org.objectweb.asm.tree.analysis.Frame;
11 |
12 | import java.util.HashSet;
13 | import java.util.Set;
14 |
15 | public class RemoveUnusedVars implements Pass, Opcodes {
16 | private static Set discoverUnused(MethodNode method) {
17 | Set unusedLocals = new HashSet<>();
18 | Set knownUsedLocals = new HashSet<>();
19 | for (AbstractInsnNode instruction : method.instructions) {
20 | if (instruction instanceof VarInsnNode vi) {
21 | int op = vi.getOpcode();
22 | if (!knownUsedLocals.contains(vi.var)) {
23 | if (op >= ISTORE && op <= ASTORE) {
24 | // not previously seen, add to potentially unused
25 | unusedLocals.add(vi.var);
26 | } else {
27 | // loads the var - this var is used!
28 | unusedLocals.remove(vi.var);
29 | knownUsedLocals.add(vi.var);
30 | }
31 | }
32 | } else if (instruction instanceof IincInsnNode ii) {
33 | if (!knownUsedLocals.contains(ii.var)) {
34 | unusedLocals.add(ii.var);
35 | }
36 | }
37 | }
38 | return unusedLocals;
39 | }
40 |
41 | @Override
42 | @SneakyThrows
43 | public void optimize(ClassNode owner, MethodNode method, Workspace wsp) {
44 | Set unusedLocals = discoverUnused(method);
45 | Analyzer bv = new Analyzer<>(new BasicInterpreter());
46 | Frame[] frames = bv.analyzeAndComputeMaxs(owner.name, method);
47 | for (AbstractInsnNode instruction : method.instructions) {
48 | if (instruction instanceof VarInsnNode vi) {
49 | if (unusedLocals.contains(vi.var)) {
50 | Frame frame = frames[method.instructions.indexOf(instruction)];
51 | if (frame == null) continue; // unreachable code, preserve
52 | BasicValue top = frame.getStack(frame.getStackSize() - 1);
53 | int size = top.getType().getSize();
54 | method.instructions.set(instruction, new InsnNode(size == 2 ? Opcodes.POP2 : Opcodes.POP));
55 | }
56 | } else if (instruction instanceof IincInsnNode ii) {
57 | if (unusedLocals.contains(ii.var)) {
58 | method.instructions.set(instruction, new InsnNode(Opcodes.NOP));
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/output/DirectoryOutputSink.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.output;
2 |
3 | import lombok.SneakyThrows;
4 |
5 | import java.io.IOException;
6 | import java.io.OutputStream;
7 | import java.nio.file.Files;
8 | import java.nio.file.Path;
9 |
10 | public class DirectoryOutputSink implements OutputSink {
11 |
12 | private final Path rootPath;
13 |
14 | public DirectoryOutputSink(Path rootPath) {
15 | this.rootPath = rootPath;
16 | }
17 |
18 | @Override
19 | public OutputStream openFile(String name) throws IOException {
20 | Path resolve = rootPath.resolve(name);
21 | Path parent = resolve.getParent();
22 | Files.createDirectories(parent);
23 | return Files.newOutputStream(resolve);
24 | }
25 |
26 | @Override
27 | public void close() {
28 | // noop
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return String.format(
34 | "%s{rootPath=%s}", getClass().getSimpleName(), this.rootPath);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/output/FsOutputSink.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.output;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 | import java.nio.file.FileSystem;
6 | import java.nio.file.Files;
7 | import java.nio.file.Path;
8 |
9 | public class FsOutputSink implements OutputSink {
10 | private final FileSystem fs;
11 |
12 | public FsOutputSink(FileSystem fs) {
13 | this.fs = fs;
14 | }
15 |
16 | @Override
17 | public OutputStream openFile(String name) throws IOException {
18 | Path path = fs.getPath(name);
19 | Path pa = path.getParent();
20 | if (pa != null) Files.createDirectories(pa);
21 | return Files.newOutputStream(path);
22 | }
23 |
24 | @Override
25 | public void close() {
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/output/JarOutputSink.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.output;
2 |
3 | import lombok.SneakyThrows;
4 | import org.jetbrains.annotations.NotNull;
5 |
6 | import java.io.IOException;
7 | import java.io.OutputStream;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.util.zip.ZipEntry;
11 | import java.util.zip.ZipOutputStream;
12 |
13 | public class JarOutputSink implements OutputSink {
14 |
15 | private final ZipOutputStream file;
16 | private final Path jarPath;
17 |
18 | @SneakyThrows
19 | public JarOutputSink(Path jarPath) {
20 | file = new ZipOutputStream(Files.newOutputStream(jarPath));
21 | this.jarPath = jarPath;
22 | }
23 |
24 | @Override
25 | public OutputStream openFile(String name) throws IOException {
26 | file.putNextEntry(new ZipEntry(name));
27 | return new EntryStream(file);
28 | }
29 |
30 | @Override
31 | public void close() throws Exception {
32 | file.close();
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | return String.format(
38 | "%s{file=%s}", getClass().getSimpleName(), this.jarPath);
39 | }
40 |
41 | private static class EntryStream extends OutputStream {
42 | private final ZipOutputStream zos;
43 |
44 | public EntryStream(ZipOutputStream zos) {
45 | this.zos = zos;
46 | }
47 |
48 | @Override
49 | public void write(int b) throws IOException {
50 | zos.write(b);
51 | }
52 |
53 | @Override
54 | public void write(byte @NotNull [] b) throws IOException {
55 | zos.write(b);
56 | }
57 |
58 | @Override
59 | public void write(byte @NotNull [] b, int off, int len) throws IOException {
60 | zos.write(b, off, len);
61 | }
62 |
63 | @Override
64 | public void close() throws IOException {
65 | zos.closeEntry();
66 | super.close();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/output/OutputSink.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.output;
2 |
3 | import java.io.IOException;
4 | import java.io.OutputStream;
5 |
6 | public interface OutputSink extends AutoCloseable {
7 | OutputStream openFile(String name) throws IOException;
8 | }
9 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/AsmClassInfo.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree;
2 |
3 | import dev.xdark.jlinker.*;
4 | import org.jetbrains.annotations.NotNull;
5 | import org.jetbrains.annotations.Nullable;
6 | import org.jetbrains.annotations.Unmodifiable;
7 | import org.objectweb.asm.tree.FieldNode;
8 | import org.objectweb.asm.tree.MethodNode;
9 |
10 | import java.util.List;
11 |
12 | public record AsmClassInfo(Workspace tree, Workspace.ClassInfo me) implements ClassModel {
13 | @Override
14 | public @NotNull String name() {
15 | return me.node().name;
16 | }
17 |
18 | @Override
19 | public int accessFlags() {
20 | return me.node().access;
21 | }
22 |
23 | @Override
24 | public @Nullable ClassModel superClass() {
25 | String sp = me.node().superName;
26 | if (sp == null) return null;
27 | Workspace.ClassInfo classInfo = tree.get(sp);
28 | return new AsmClassInfo(tree, classInfo);
29 | }
30 |
31 | @Override
32 | public @NotNull @Unmodifiable Iterable extends @NotNull ClassModel> interfaces() {
33 | List itfs = me.node().interfaces;
34 | return itfs.stream().map(it -> new AsmClassInfo(tree, tree.get(it))).toList();
35 | }
36 |
37 | @Override
38 | public @Nullable AsmMethodInfo findMethod(@NotNull String name, @NotNull MethodDescriptor descriptor) {
39 | List methods = me.node().methods;
40 | String descriptorString = descriptor.toString();
41 | for (MethodNode m : methods) {
42 | if (name.equals(m.name) && descriptorString.equals(m.desc)) {
43 | return new AsmMethodInfo(this, m);
44 | }
45 | }
46 | return null;
47 | }
48 |
49 | @Override
50 | public @Nullable AsmFieldInfo findField(@NotNull String name, @NotNull FieldDescriptor descriptor) {
51 | List methods = me.node().fields;
52 | String descriptorString = descriptor.toString();
53 | for (FieldNode m : methods) {
54 | if (name.equals(m.name) && descriptorString.equals(m.desc)) {
55 | return new AsmFieldInfo(this, m);
56 | }
57 | }
58 | return null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/AsmFieldInfo.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree;
2 |
3 | import dev.xdark.jlinker.FieldModel;
4 | import org.objectweb.asm.tree.FieldNode;
5 |
6 | public record AsmFieldInfo(AsmClassInfo owner, FieldNode me) implements FieldModel {
7 | @Override
8 | public int accessFlags() {
9 | return me.access;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/AsmMethodInfo.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree;
2 |
3 | import dev.xdark.jlinker.MethodModel;
4 | import org.objectweb.asm.tree.MethodNode;
5 |
6 | public record AsmMethodInfo(AsmClassInfo owner, MethodNode me) implements MethodModel {
7 |
8 | @Override
9 | public int accessFlags() {
10 | return me.access;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/Pair.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 | import lombok.Setter;
6 |
7 | @AllArgsConstructor
8 | @Getter
9 | @Setter
10 | public class Pair {
11 | A a;
12 | B b;
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/SmartClassWriter.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree;
2 |
3 | import org.objectweb.asm.ClassWriter;
4 | import org.objectweb.asm.Type;
5 |
6 | /**
7 | * Class writer, that does not load the classes it needs to find frames for with the regular class loader, but uses the
8 | * jar that contains the classes to find common types instead. Prevents arbitrary code execution.
9 | */
10 | public class SmartClassWriter extends ClassWriter {
11 | final Workspace jar;
12 | private final Remapper remapper;
13 |
14 | public SmartClassWriter(int flags, Workspace jar, Remapper remapper) {
15 | super(flags);
16 | this.jar = jar;
17 | this.remapper = remapper;
18 | }
19 |
20 | @Override
21 | protected String getCommonSuperClass(final String type1M, final String type2M) {
22 | // we get remapped types here
23 | String type1 = remapper.unmapClassName(type1M);
24 | String type2 = remapper.unmapClassName(type2M);
25 |
26 | Type commonTop = jar.findCommonSupertype(Type.getObjectType(type1), Type.getObjectType(type2));
27 | return remapper.map(commonTop.getInternalName());
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/resolver/DirectoryResolver.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree.resolver;
2 |
3 | import org.objectweb.asm.ClassReader;
4 | import org.objectweb.asm.tree.ClassNode;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.nio.file.Files;
10 | import java.nio.file.Path;
11 |
12 | public class DirectoryResolver extends Resolver {
13 |
14 | private final Path rootPath;
15 |
16 | public DirectoryResolver(Path rootPath) {
17 | this.rootPath = rootPath;
18 | }
19 |
20 | @Override
21 | public void close() {
22 | // noop
23 | }
24 |
25 | @Override
26 | public String toString() {
27 | return "DirectoryResolver(rootPath=" + rootPath + ")";
28 | }
29 |
30 | @Override
31 | protected ClassNode resolveInner(String name) throws IOException {
32 | Path path = rootPath.resolve(name + ".class");
33 | if (!Files.exists(path)) return null;
34 | byte[] sig = new byte[4];
35 | try (InputStream inputStream = Files.newInputStream(path)) {
36 | int read = inputStream.read(sig);
37 | if (read != 4) return null;
38 | if (sig[0] != (byte) 0xCA || sig[1] != (byte) 0xFE || sig[2] != (byte) 0xBA || sig[3] != (byte) 0xBE)
39 | return null;
40 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
41 | baos.write(sig);
42 | inputStream.transferTo(baos);
43 | ClassReader cr = new ClassReader(baos.toByteArray());
44 | ClassNode cn = new ClassNode();
45 | cr.accept(cn, ClassReader.SKIP_FRAMES);
46 | return cn;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/resolver/FsResolver.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree.resolver;
2 |
3 | import org.objectweb.asm.ClassReader;
4 | import org.objectweb.asm.tree.ClassNode;
5 |
6 | import java.io.IOException;
7 | import java.nio.ByteBuffer;
8 | import java.nio.channels.SeekableByteChannel;
9 | import java.nio.file.FileSystem;
10 | import java.nio.file.Files;
11 | import java.nio.file.Path;
12 | import java.nio.file.StandardOpenOption;
13 |
14 | public class FsResolver extends Resolver {
15 | private final FileSystem fs;
16 |
17 | public FsResolver(FileSystem fs) {
18 | this.fs = fs;
19 | }
20 |
21 | @Override
22 | public void close() throws Exception {
23 | fs.close();
24 | }
25 |
26 | @Override
27 | public String toString() {
28 | return "JarResolver(" + fs + ")";
29 | }
30 |
31 | @Override
32 | protected ClassNode resolveInner(String name) throws IOException {
33 | Path path = fs.getPath(name + ".class");
34 | if (!Files.exists(path)) return null;
35 | try (SeekableByteChannel seekableByteChannel = Files.newByteChannel(path, StandardOpenOption.READ)) {
36 | if (seekableByteChannel.size() <= 4) return null;
37 | ByteBuffer sig = ByteBuffer.allocate(4);
38 | seekableByteChannel.read(sig);
39 | sig.flip();
40 | int anInt = sig.getInt();
41 | if (anInt != 0xCAFEBABE) {
42 | return null; // not a class
43 | }
44 | long remaining = seekableByteChannel.size() - seekableByteChannel.position();
45 | ByteBuffer allocate = ByteBuffer.allocate((int) remaining);
46 | seekableByteChannel.read(allocate);
47 | allocate.flip();
48 | byte[] array = allocate.array();
49 | ClassReader cr = new ClassReader(array, -4, 0);
50 | ClassNode cn = new ClassNode();
51 | cr.accept(cn, ClassReader.SKIP_FRAMES);
52 | return cn;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/resolver/JmodResolver.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree.resolver;
2 |
3 | import org.objectweb.asm.ClassReader;
4 | import org.objectweb.asm.tree.ClassNode;
5 |
6 | import java.io.ByteArrayOutputStream;
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.lang.module.ModuleReader;
10 | import java.lang.module.ModuleReference;
11 | import java.nio.file.Path;
12 | import java.util.Optional;
13 |
14 | public class JmodResolver extends Resolver {
15 | private final ModuleReader mr;
16 | private final Path path;
17 |
18 | public JmodResolver(ModuleReference md) throws IOException {
19 | mr = md.open();
20 | path = md.location().map(Path::of).orElse(Path.of("jmod", String.valueOf(md.descriptor().hashCode())));
21 | }
22 |
23 | @Override
24 | public void close() throws Exception {
25 | mr.close();
26 | }
27 |
28 | @Override
29 | public String toString() {
30 | return "JmodResolver(repr=" + path + ")";
31 | }
32 |
33 | @Override
34 | protected ClassNode resolveInner(String name) throws IOException {
35 | Optional open = mr.open(name + ".class");
36 | if (open.isEmpty()) return null;
37 | byte[] sig = new byte[4];
38 | try (InputStream inputStream = open.get()) {
39 | int read = inputStream.read(sig);
40 | if (read != 4) return null;
41 | if (sig[0] != (byte) 0xCA || sig[1] != (byte) 0xFE || sig[2] != (byte) 0xBA || sig[3] != (byte) 0xBE)
42 | return null;
43 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
44 | baos.write(sig);
45 | inputStream.transferTo(baos);
46 | ClassReader cr = new ClassReader(baos.toByteArray());
47 | ClassNode cn = new ClassNode();
48 | cr.accept(cn, 0); // keep all meta for these ones
49 | return cn;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/resolver/Resolver.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree.resolver;
2 |
3 | import lombok.SneakyThrows;
4 | import org.objectweb.asm.tree.ClassNode;
5 |
6 | import java.io.IOException;
7 | import java.lang.module.ModuleFinder;
8 | import java.lang.module.ModuleReference;
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | public abstract class Resolver implements AutoCloseable {
13 | private static Resolver stdlibRes;
14 |
15 | public static Resolver stdlibResolver() throws IOException {
16 | if (stdlibRes == null) {
17 | List jarResolvers = new ArrayList<>();
18 | for (ModuleReference moduleReference : ModuleFinder.ofSystem().findAll()) {
19 | jarResolvers.add(new JmodResolver(moduleReference));
20 | }
21 | stdlibRes = new UnionResolver(jarResolvers.toArray(Resolver[]::new));
22 | }
23 | return stdlibRes;
24 | }
25 |
26 | @SneakyThrows
27 | public ClassNode resolve(String name) {
28 | return resolveInner(name);
29 | }
30 |
31 | protected abstract ClassNode resolveInner(String name) throws IOException;
32 | }
33 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/tree/resolver/UnionResolver.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.tree.resolver;
2 |
3 | import org.objectweb.asm.tree.ClassNode;
4 |
5 | import java.util.Arrays;
6 | import java.util.stream.Collectors;
7 |
8 | public class UnionResolver extends Resolver {
9 |
10 | private final Resolver[] resolvers;
11 |
12 | public UnionResolver(Resolver... resolvers) {
13 | this.resolvers = resolvers;
14 | }
15 |
16 | @Override
17 | public void close() throws Exception {
18 | for (Resolver resolver : resolvers) {
19 | resolver.close();
20 | }
21 | }
22 |
23 | @Override
24 | public String toString() {
25 | return "Union(" + Arrays.stream(resolvers).map(Object::toString).collect(Collectors.joining(", ")) + ")";
26 | }
27 |
28 | @Override
29 | protected ClassNode resolveInner(String name) {
30 | for (Resolver resolver : resolvers) {
31 | ClassNode resolve = resolver.resolve(name);
32 | if (resolve != null) return resolve;
33 | }
34 | return null;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/ClassFilter.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import java.util.regex.Pattern;
4 |
5 | public record ClassFilter(Pattern p) {
6 | public static Pattern parseGlob(String s) {
7 | StringBuilder tx = new StringBuilder("^");
8 | compilePatternInternal(s, tx);
9 | tx.append("$");
10 | return Pattern.compile(tx.toString());
11 | }
12 |
13 | public static void compilePatternInternal(String s, StringBuilder tx) {
14 | int length = s.length();
15 | StringBuilder stringToQuote = new StringBuilder();
16 | for (int i = 0; i < length; i++) {
17 | char chr = s.charAt(i);
18 | if (chr == '*') {
19 | if (!stringToQuote.isEmpty()) {
20 | tx.append(Pattern.quote(stringToQuote.toString()));
21 | stringToQuote = new StringBuilder();
22 | }
23 | int level = 0;
24 | while (i < length && s.charAt(i) == '*') {
25 | level++;
26 | i++;
27 | }
28 | i--; // backtrack once to get back into the regular for loop cycle
29 | if (level == 1) {
30 | tx.append("[^/]*?");
31 | } else if (level == 2) {
32 | tx.append(".*?");
33 | } else {
34 | throw new IllegalStateException("Unknown star formation '" + "*".repeat(level) + "'");
35 | }
36 | } else if (chr == '?') {
37 | if (!stringToQuote.isEmpty()) {
38 | tx.append(Pattern.quote(stringToQuote.toString()));
39 | stringToQuote = new StringBuilder();
40 | }
41 | // one character that cant cross boundaries
42 | tx.append("[^/]");
43 | } else {
44 | stringToQuote.append(chr);
45 | }
46 | }
47 | if (!stringToQuote.isEmpty()) {
48 | tx.append(Pattern.quote(stringToQuote.toString()));
49 | }
50 | }
51 |
52 | public static ClassFilter fromString(String s) {
53 | return new ClassFilter(parseGlob(s.replace('.', '/')));
54 | }
55 |
56 | public boolean matches(String internalName) {
57 | return p.matcher(internalName).matches();
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/Graph.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import lombok.Getter;
4 |
5 | import java.io.PrintStream;
6 | import java.util.HashMap;
7 | import java.util.HashSet;
8 | import java.util.Map;
9 | import java.util.Set;
10 |
11 | @Getter
12 | public class Graph {
13 | final
14 | Set nodes = new HashSet<>();
15 | final
16 | Set> edges = new HashSet<>();
17 |
18 | public void add(T t) {
19 | nodes.add(t);
20 | }
21 |
22 | public void addEdge(T a, T b) {
23 | if (edges.stream().anyMatch(e -> e.a.equals(a) && e.b.equals(b))) return;
24 | edges.add(new Edge<>(a, b));
25 | }
26 |
27 | public void remove(T node) {
28 | nodes.remove(node);
29 | edges.removeIf(v -> v.a.equals(node) || v.b.equals(node));
30 | }
31 |
32 | public void writeDotFormat(PrintStream stream) {
33 | stream.println("digraph {");
34 | Map idMap = new HashMap<>();
35 | int counter = 0;
36 | for (T node : nodes) {
37 | int id = counter++;
38 | idMap.put(node, id);
39 | stream.append("n").append(String.valueOf(id)).append("[label=\"").append(Util.escapeString(node.getContent().toCharArray())).println("\",shape=box];");
40 | }
41 | for (Edge edge : edges) {
42 | stream.append("n").append(String.valueOf(idMap.get(edge.a))).append(" -> ").append("n").append(String.valueOf(idMap.get(edge.b))).println(";");
43 | }
44 | stream.println("}");
45 | }
46 |
47 | public interface Node {
48 | String getContent();
49 | }
50 |
51 | public record Edge(T a, T b) {
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/InstructionPatchList.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import org.objectweb.asm.tree.AbstractInsnNode;
4 | import org.objectweb.asm.tree.InsnList;
5 | import org.objectweb.asm.tree.LabelNode;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | public class InstructionPatchList {
11 | private final Map clonedInsns;
12 | private final Map inverseCloned;
13 | private final InsnList clonedIl;
14 | private final InsnList originalList;
15 |
16 | public InstructionPatchList(InsnList originalList) {
17 | Map labelMap = Util.cloneLabels(originalList);
18 | clonedInsns = new HashMap<>();
19 | clonedIl = new InsnList();
20 | inverseCloned = new HashMap<>();
21 | this.originalList = originalList;
22 | for (AbstractInsnNode abstractInsnNode : originalList) {
23 | AbstractInsnNode cloned = abstractInsnNode.clone(labelMap);
24 | clonedInsns.put(abstractInsnNode, cloned);
25 | inverseCloned.put(cloned, abstractInsnNode);
26 | clonedIl.add(cloned);
27 | }
28 | }
29 |
30 |
31 | public void insertBefore(AbstractInsnNode insn, AbstractInsnNode toInsert) {
32 | clonedIl.insertBefore(clonedInsns.getOrDefault(insn, insn), toInsert);
33 | }
34 |
35 |
36 | public void insertBefore(AbstractInsnNode insn, InsnList toInsert) {
37 | clonedIl.insertBefore(clonedInsns.getOrDefault(insn, insn), toInsert);
38 | }
39 |
40 |
41 | public AbstractInsnNode get(int index) {
42 | AbstractInsnNode cl = clonedIl.get(index);
43 | return inverseCloned.getOrDefault(cl, cl);
44 | }
45 |
46 |
47 | public int size() {
48 | return clonedIl.size();
49 | }
50 |
51 |
52 | public AbstractInsnNode getFirst() {
53 | AbstractInsnNode first = clonedIl.getFirst();
54 | return inverseCloned.getOrDefault(first, first);
55 | }
56 |
57 |
58 | public AbstractInsnNode getLast() {
59 | AbstractInsnNode first = clonedIl.getLast();
60 | return inverseCloned.getOrDefault(first, first);
61 | }
62 |
63 |
64 | public boolean contains(AbstractInsnNode insnNode) {
65 | return clonedIl.contains(clonedInsns.getOrDefault(insnNode, insnNode));
66 | }
67 |
68 |
69 | public int indexOf(AbstractInsnNode insnNode) {
70 | return clonedIl.indexOf(clonedInsns.getOrDefault(insnNode, insnNode));
71 | }
72 |
73 |
74 | public void set(AbstractInsnNode oldInsnNode, AbstractInsnNode newInsnNode) {
75 | clonedIl.set(clonedInsns.getOrDefault(oldInsnNode, oldInsnNode), newInsnNode);
76 | }
77 |
78 | public void set(AbstractInsnNode insn, InsnList replacement) {
79 | AbstractInsnNode d = clonedInsns.getOrDefault(insn, insn);
80 | clonedIl.insertBefore(d, replacement);
81 | clonedIl.remove(d);
82 | }
83 |
84 |
85 | public void add(InsnList insnList) {
86 | clonedIl.add(insnList);
87 | }
88 |
89 |
90 | public void add(AbstractInsnNode insnNode) {
91 | clonedIl.add(insnNode);
92 | }
93 |
94 |
95 | public void insert(InsnList insnList) {
96 | clonedIl.insert(insnList);
97 | }
98 |
99 |
100 | public void insert(AbstractInsnNode insnNode) {
101 | clonedIl.insert(insnNode);
102 | }
103 |
104 |
105 | public void insert(AbstractInsnNode oldInsnNode, InsnList insnList) {
106 | clonedIl.insert(clonedInsns.getOrDefault(oldInsnNode, oldInsnNode), insnList);
107 | }
108 |
109 |
110 | public void insert(AbstractInsnNode oldInsnNode, AbstractInsnNode insnNode) {
111 | clonedIl.insert(clonedInsns.getOrDefault(oldInsnNode, oldInsnNode), insnNode);
112 | }
113 |
114 |
115 | public void remove(AbstractInsnNode oldInsnNode) {
116 | clonedIl.remove(clonedInsns.getOrDefault(oldInsnNode, oldInsnNode));
117 | }
118 |
119 |
120 | public void clear() {
121 | clonedIl.clear();
122 | inverseCloned.clear();
123 | clonedInsns.clear();
124 | }
125 |
126 | public void apply() {
127 | this.originalList.clear();
128 | for (AbstractInsnNode abstractInsnNode : clonedIl) {
129 | // either we have a mapping from the cloned insn to the original,
130 | // or this insn was inserted manually after construction
131 | AbstractInsnNode originalNode = inverseCloned.getOrDefault(abstractInsnNode, abstractInsnNode);
132 | this.originalList.add(originalNode);
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/InvalidCodeGuard.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import lombok.extern.log4j.Log4j2;
4 | import org.objectweb.asm.commons.CodeSizeEvaluator;
5 | import org.objectweb.asm.tree.ClassNode;
6 | import org.objectweb.asm.tree.MethodNode;
7 |
8 | import java.util.Optional;
9 |
10 | @Log4j2
11 | public class InvalidCodeGuard {
12 | private ClassNode originalClass = new ClassNode();
13 | private static final StackWalker sw = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
14 |
15 | public void init(ClassNode mn) {
16 | originalClass = new ClassNode();
17 | mn.accept(originalClass);
18 | }
19 |
20 | public boolean checkAndRestoreIfNeeded(ClassNode cn, boolean log) {
21 | boolean any = false;
22 | for (MethodNode origOkNode : originalClass.methods) {
23 | CodeSizeEvaluator cse = new CodeSizeEvaluator(null);
24 | Optional any1 = cn.methods.stream()
25 | .filter(f -> f.name.equals(origOkNode.name) && f.desc.equals(origOkNode.desc)).findAny();
26 | if (any1.isEmpty()) continue; // method was deleted? ok sure
27 | MethodNode methodNode = any1.get();
28 | methodNode.accept(cse);
29 | if (cse.getMaxSize() >= 0xFFFF) {
30 | // origOkNode.accept(methodNode);
31 | MethodNode copy = Util.emptyCopyOf(methodNode);
32 | origOkNode.accept(copy);
33 | cn.methods.set(cn.methods.indexOf(methodNode), copy);
34 | if (log) {
35 | Class> caller = sw.getCallerClass();
36 | InvalidCodeGuard.log.warn("{} generated too much code for method {}.{}{} ({} bytes). Rolling back...", caller.getSimpleName(), cn.name, methodNode.name, methodNode.desc, cse.getMaxSize());
37 | CodeSizeEvaluator cs = new CodeSizeEvaluator(null);
38 | copy.accept(cs);
39 | InvalidCodeGuard.log.info(cs.getMaxSize());
40 | }
41 | any = true;
42 | }
43 | }
44 | return any;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/MemberFilter.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import me.x150.j2cc.conf.Configuration;
4 | import org.objectweb.asm.Type;
5 |
6 | import java.util.regex.Pattern;
7 |
8 | public record MemberFilter(ClassFilter cf, Pattern name, Pattern type) {
9 | public static void main(String[] args) {
10 | Pattern pdx = parseDescriptorGlob("(IILjava.**/String;?)?");
11 | System.out.println(pdx);
12 | System.out.println(pdx.matcher("(IILjava/lang/String;D)V").matches());
13 | System.out.println(pdx.matcher("(IILjava/lang/aaa/String;J)V").matches());
14 | System.out.println(pdx.matcher("(IILjava/lguh/String;D)V").matches());
15 | System.out.println(pdx.matcher("(IILjava/lguh/String;)V").matches());
16 | System.out.println(pdx.matcher("(IILjava/lguh/String;D)Ljava/lang/Guh;").matches());
17 | Pattern real = parseDescriptorGlob("?");
18 | System.out.println(real);
19 | System.out.println(real.matcher("Ljava/lang/String;").matches());
20 | System.out.println(real.matcher("I").matches());
21 | }
22 |
23 | public static MemberFilter fromFilter(Configuration.Member f) {
24 | ClassFilter classFilter = ClassFilter.fromString(f.getClazz());
25 | Pattern name = ClassFilter.parseGlob(f.getMemberName());
26 | Pattern type = parseDescriptorGlob(f.getDescriptor());
27 | return new MemberFilter(classFilter, name, type);
28 | }
29 |
30 | private static int parseType(StringBuilder tx, String s, int index, char currentChar) {
31 | return switch (currentChar) {
32 | case '[' -> {
33 | int nDims = 0;
34 | int j;
35 | for(j = index; j < s.length(); j++) {
36 | if (s.charAt(j) == '[') {
37 | nDims++;
38 | } else break;
39 | }
40 | if (nDims <= 2) tx.append("\\[".repeat(nDims));
41 | else tx.append(Pattern.quote("[".repeat(nDims)));
42 | yield parseType(tx, s, j, s.charAt(j)) + nDims;
43 | }
44 | case 'L' -> {
45 | tx.append("L");
46 | // class ref, read until ;
47 | int endOfRef = s.indexOf(';', index);
48 | // for this one, normal type glob rules apply
49 | StringBuilder stringToQuote = new StringBuilder();
50 | for (int i = index + 1; i < endOfRef; i++) {
51 | char chr = s.charAt(i);
52 | if (chr == '*') {
53 | if (!stringToQuote.isEmpty()) {
54 | tx.append(Pattern.quote(stringToQuote.toString()));
55 | stringToQuote = new StringBuilder();
56 | }
57 | int level = 0;
58 | while (i < endOfRef && s.charAt(i) == '*') {
59 | level++;
60 | i++;
61 | }
62 | i--; // backtrack once to get back into the regular for loop cycle
63 | if (level == 1) {
64 | // one part *, allow one name element to be anything but not / or ;
65 | tx.append("[^/;]*?");
66 | } else if (level == 2) {
67 | // two part *, allow one name element to be anything including / but not ;
68 | tx.append("[^;]*?");
69 | } else {
70 | throw new IllegalStateException("Unknown star formation '" + "*".repeat(level) + "'");
71 | }
72 | } else if (chr == '?') {
73 | if (!stringToQuote.isEmpty()) {
74 | tx.append(Pattern.quote(stringToQuote.toString()));
75 | stringToQuote = new StringBuilder();
76 | }
77 | // one character that cant cross boundaries
78 | tx.append("[^/;]");
79 | } else {
80 | stringToQuote.append(chr == '.' ? '/' : chr);
81 | }
82 | }
83 | if (!stringToQuote.isEmpty()) {
84 | tx.append(Pattern.quote(stringToQuote.toString()));
85 | }
86 | tx.append(";");
87 | yield (endOfRef + 1) - index; // skip over ; to next element
88 | }
89 | // any type
90 | // either any prim type (incl. V, see case below for reason), or L;
91 | case '?' -> {
92 | tx.append("([ZCBSIFJDV]|L.+?;)");
93 | yield 1;
94 | }
95 | // prim type, just add as regular
96 | case 'Z', 'B', 'C', 'S', 'I', 'F', 'J', 'D',
97 | 'V' /* technically not allowed but we'll do it anyway for CQ */ -> {
98 | tx.append(currentChar);
99 | yield 1;
100 | }
101 | default -> throw new IllegalStateException("Unknown type character " + currentChar);
102 | };
103 | }
104 |
105 | public static Pattern parseDescriptorGlob(String s) {
106 | StringBuilder tx = new StringBuilder("^");
107 | char firstChar = s.charAt(0);
108 | if (firstChar == '(') {
109 | tx.append("\\(");
110 | // method descriptor
111 | char currentChar;
112 | int index = 1;
113 | while ((currentChar = s.charAt(index)) != ')') {
114 | index += parseType(tx, s, index, currentChar);
115 | }
116 | tx.append("\\)");
117 | // parse return value
118 | // +1: skip over ending )
119 | parseType(tx, s, index + 1, s.charAt(index + 1));
120 | } else {
121 | // field descriptor
122 | parseType(tx, s, 0, firstChar);
123 | }
124 | tx.append("$");
125 | return Pattern.compile(tx.toString());
126 | }
127 |
128 | public boolean matches(String cl, String name, Type type) {
129 | return cf.matches(cl) && this.name.matcher(name).matches() && this.type.matcher(type.getDescriptor()).matches();
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/NameGenerator.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import lombok.RequiredArgsConstructor;
4 |
5 | import java.util.Arrays;
6 | import java.util.stream.Collectors;
7 |
8 | @RequiredArgsConstructor
9 | public class NameGenerator {
10 | public static final String ALPH_LOWER = "abcdefghijklmnopqrstuvwxyz";
11 | public static final String ALPH_UPPER = ALPH_LOWER.toUpperCase();
12 | public static final String ALPH_LOWER_UPPER = ALPH_LOWER + ALPH_UPPER;
13 | final String dictionary;
14 | long counter = 0;
15 |
16 | public static void main(String[] args) {
17 | NameGenerator ng = new NameGenerator("abcd");
18 | for (int i = 0; i < 5000; i++) {
19 | ng.nextName();
20 | }
21 | }
22 |
23 | public String nextName() {
24 | long n = ++counter;
25 | long base = dictionary.length();
26 | if (n < base) {
27 | return String.valueOf(dictionary.charAt((int) n - 1));
28 | }
29 | int length = (int) Math.ceil(Math.log(n) / Math.log(base));
30 | int[] idx = new int[length];
31 | Arrays.fill(idx, -1);
32 | while (n > 0) {
33 | idx[idx.length - 1]++;
34 | n--;
35 | for (int i = idx.length - 1; i >= 0; i--) {
36 | if (idx[i] >= base) {
37 | idx[i - 1]++;
38 | idx[i] = 0;
39 | }
40 | }
41 | }
42 | return Arrays.stream(idx)
43 | .filter(value -> value != -1)
44 | .mapToObj(value -> String.valueOf(dictionary.charAt(value)))
45 | .collect(Collectors.joining());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/Pair.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import lombok.*;
4 |
5 | @AllArgsConstructor
6 | @Getter
7 | @Setter
8 | @EqualsAndHashCode
9 | @ToString
10 | public class Pair {
11 | A a;
12 | B b;
13 | }
14 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/StringCollector.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import it.unimi.dsi.fastutil.objects.Object2IntMap;
4 | import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
5 | import lombok.SneakyThrows;
6 | import me.x150.j2cc.util.natives.chacha20_h;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.io.IOException;
10 | import java.io.OutputStream;
11 | import java.lang.foreign.Arena;
12 | import java.lang.foreign.MemorySegment;
13 |
14 | public class StringCollector {
15 | private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
16 | private final Object2IntMap cache = new Object2IntOpenHashMap<>();
17 | public int reserveString(String s) {
18 | synchronized (cache) {
19 | return cache.computeIfAbsent(s, this::createStringIndex);
20 | }
21 | }
22 |
23 | @SneakyThrows
24 | private synchronized int createStringIndex(String s) {
25 | int current = baos.size();
26 | baos.write(Util.encodeMUTF(s));
27 | baos.write(0);
28 | return current;
29 | }
30 |
31 | public void writeEncryptedPoolTo(OutputStream os, byte[] key) throws IOException {
32 | try (Arena arena = Arena.ofConfined()) {
33 | MemorySegment keyData = arena.allocateFrom(chacha20_h.C_CHAR, key);
34 | int nElements = baos.size();
35 | long n = Math.ceilDiv(nElements, 64L);
36 | MemorySegment chachaBuffer = arena.allocate(chacha20_h.C_CHAR, n * 64L);
37 | chacha20_h.fuckMyShitUp(keyData, chachaBuffer, n);
38 | byte[] array = chachaBuffer.toArray(chacha20_h.C_CHAR);
39 | byte[] byteArray = baos.toByteArray();
40 | for (int i = 0; i < byteArray.length; i++) {
41 | os.write(byteArray[i] ^ array[i]);
42 | }
43 | }
44 | }
45 |
46 | public int size() {
47 | return baos.size();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/core/src/main/java/me/x150/j2cc/util/simulation/SimulatorValue.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util.simulation;
2 |
3 | import me.x150.j2cc.tree.Workspace;
4 | import me.x150.j2cc.util.Util;
5 | import org.objectweb.asm.Type;
6 | import org.objectweb.asm.tree.AbstractInsnNode;
7 | import org.objectweb.asm.tree.analysis.AnalyzerException;
8 | import org.objectweb.asm.tree.analysis.Value;
9 |
10 | import java.util.Objects;
11 |
12 | public record SimulatorValue(Type type, boolean valueKnown, Object value) implements Value {
13 | public static final SimulatorValue UNKNOWN = new SimulatorValue(null, false, null);
14 | public static final SimulatorValue TWO_WORD_2ND = new SimulatorValue(null, false, null);
15 | public static final SimulatorValue UNINITIALIZED = new SimulatorValue(null, false, null);
16 | public static final SimulatorValue NULL = new SimulatorValue(Util.OBJECT_TYPE, true, null);
17 |
18 | @Override
19 | public int getSize() {
20 | return type == null ? 1 : type.getSize();
21 | }
22 |
23 | public T valueAs(AbstractInsnNode where, Class type) throws AnalyzerException {
24 | if (!valueKnown)
25 | throw new AnalyzerException(where, "(internal exception) Expected a stack element to be known, but wasnt. This is probably a programming oversight.");
26 | if (!type.isInstance(value)) {
27 | throw new AnalyzerException(where, "Expected a stack element to be of type " + type + ", but found " + this.type + " (represented as " + (value == null ? "" : value.getClass()) + ": " + value + ")");
28 | }
29 | return type.cast(value);
30 | }
31 |
32 | public SimulatorValue merge(Workspace wsp, SimulatorValue other) {
33 | if (this.type == null || other.type == null) return UNKNOWN; // merge(U, ?) = U
34 | if (this.type.getSort() != other.type.getSort()) {
35 | if ((this.type.getSort() == Type.ARRAY && Util.OBJECT_TYPE.equals(other.type))
36 | || (Util.OBJECT_TYPE.equals(this.type) && other.type.getSort() == Type.ARRAY)) {
37 | // merge(Object, ?[]) = Object
38 | return new SimulatorValue(Util.OBJECT_TYPE, false, null);
39 | }
40 | // we dont agree on a type
41 | return UNKNOWN;
42 | }
43 |
44 | // at this point, we have the same sort of type. if it's actually the same or a related type is up for us to find out now
45 |
46 | if (this == NULL && other == NULL) {
47 | // both are null, return null
48 | return NULL;
49 | } else if (this == NULL) {
50 | // we're null so we take the other as base
51 | return new SimulatorValue(other.type, false, null);
52 | } else if (other == NULL) {
53 | // the other's a null value so we have the talking stick
54 | return new SimulatorValue(this.type, false, null);
55 | }
56 |
57 | Type mergedType = this.type; // in case this isn't an object or array, the type already matches
58 | if (this.type.getSort() >= Type.ARRAY) {
59 | // we're both objects, find the common parent
60 | mergedType = wsp.findCommonSupertype(this.type, other.type);
61 | }
62 |
63 | if (this.valueKnown != other.valueKnown) {
64 | // we know the value but the other one doesnt, we cant say we can unify both of these statements
65 | return new SimulatorValue(mergedType, false, null);
66 | }
67 | if (this.valueKnown) {
68 | if (!Objects.equals(this.value, other.value))
69 | return new SimulatorValue(mergedType, false, null);
70 | }
71 | // we're the same value
72 | return new SimulatorValue(mergedType, this.valueKnown, this.value);
73 | }
74 |
75 |
76 | @Override
77 | public String toString() {
78 | return "SimulatorValue[" +
79 | "type=" + type + ", " +
80 | "valueKnown=" + valueKnown + ", " +
81 | "value=" + value + ']';
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/core/src/main/resources/log4j2.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/core/src/main/resources/logo.txt:
--------------------------------------------------------------------------------
1 | /$$$$$$
2 | /$$__ $$
3 | /$$|__/ \ $$ /$$$$$$$ /$$$$$$$
4 | |__/ /$$$$$$/ /$$_____/ /$$_____/
5 | /$$ /$$____/ | $$ | $$
6 | | $$| $$ | $$ | $$
7 | | $$| $$$$$$$$| $$$$$$$| $$$$$$$
8 | | $$|________/ \_______/ \_______/
9 | /$$ | $$
10 | | $$$$$$/
11 | \______/
12 |
--------------------------------------------------------------------------------
/core/src/main/resources/memo.txt:
--------------------------------------------------------------------------------
1 | * ,MMM8&&&. *
2 | MMMM88&&&&& .
3 | MMMM88&&&&&&&
4 | * MMM88&&&&&&&&
5 | MMM88&&&&&&&&
6 | 'MMM88&&&&&&'
7 | 'MMM8&&&' *
8 | |\___/|
9 | ) ( As a memorial to Lucky,
10 | =\ /= the best cat to have ever lived.
11 | )===( * May you rest in peace
12 | / \ on the evergreen fields.
13 | | |
14 | / \
15 | \ /
16 | _/\_/\_/\__ _/_/\_/\_/\_/\_/\_/\_/\_/\_/\_
17 | | | | |( ( | | | | | | | | | |
18 | | | | | ) ) | | | | | | | | | |
19 | | | | |(_( | | | | | | | | | |
20 | | | | | | | | | | | | | | | |
21 | jgs| | | | | | | | | | | | | |
--------------------------------------------------------------------------------
/core/src/test/java/me/x150/j2cc/util/InstructionPatchListTest.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.objectweb.asm.Opcodes;
5 | import org.objectweb.asm.tree.AbstractInsnNode;
6 | import org.objectweb.asm.tree.InsnList;
7 | import org.objectweb.asm.tree.InsnNode;
8 | import org.objectweb.asm.tree.LdcInsnNode;
9 |
10 | import static org.junit.jupiter.api.Assertions.*;
11 |
12 | class InstructionPatchListTest {
13 |
14 | @Test
15 | public void testFunctionality() {
16 | InsnList originalList = new InsnList();
17 | LdcInsnNode theActualLdc = new LdcInsnNode("hi");
18 | originalList.add(theActualLdc);
19 |
20 | InstructionPatchList ipl = new InstructionPatchList(originalList);
21 | AbstractInsnNode theLdc = ipl.get(0);
22 | assertSame(theActualLdc, theLdc);
23 |
24 | InsnNode ic = new InsnNode(Opcodes.ICONST_0);
25 | ipl.insertBefore(theActualLdc, ic);
26 |
27 | assertSame(ic, ipl.get(0));
28 |
29 | assertFalse(originalList.contains(ic));
30 |
31 | assertTrue(ipl.contains(theActualLdc));
32 |
33 | ipl.apply();
34 |
35 | assertTrue(originalList.contains(ic));
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/core/src/test/java/me/x150/j2cc/util/RemapperTest.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.util;
2 |
3 | import me.x150.j2cc.tree.Remapper;
4 | import me.x150.j2cc.tree.Workspace;
5 | import me.x150.j2cc.tree.resolver.Resolver;
6 | import org.junit.jupiter.api.Test;
7 | import org.objectweb.asm.Opcodes;
8 | import org.objectweb.asm.Type;
9 | import org.objectweb.asm.commons.ClassRemapper;
10 | import org.objectweb.asm.tree.ClassNode;
11 | import org.objectweb.asm.tree.MethodInsnNode;
12 | import org.objectweb.asm.tree.MethodNode;
13 |
14 | import java.io.IOException;
15 | import java.util.Arrays;
16 | import java.util.Collection;
17 | import java.util.List;
18 | import java.util.stream.Stream;
19 |
20 | import static org.junit.jupiter.api.Assertions.assertEquals;
21 |
22 | class RemapperTest implements Opcodes {
23 | private static final String OBJECT = Type.getInternalName(Object.class);
24 |
25 | private static ClassNode createDummyClass(String name, String parent, String... itfs) {
26 | ClassNode cn = new ClassNode();
27 | cn.visit(Opcodes.V11, ACC_PUBLIC, name, null, parent, itfs);
28 | return cn;
29 | }
30 |
31 | private static MethodNode createDummyMethod(ClassNode cn, String name, @SuppressWarnings("SameParameterValue") String desc) {
32 | return (MethodNode) cn.visitMethod(ACC_PUBLIC, name, desc, null, null);
33 | }
34 |
35 | private static Workspace workspaceOf(ClassNode... cn) throws IOException {
36 | return new Workspace(
37 | cn,
38 | Resolver.stdlibResolver()
39 | );
40 | }
41 |
42 | private static Collection getInfos(Workspace wsp, ClassNode... cn) {
43 | return Arrays.stream(cn).map(it -> wsp.get(it.name)).toList();
44 | }
45 |
46 | @Test
47 | public void testGeneral() throws Throwable {
48 |
49 | ClassNode ext = createDummyClass("External", OBJECT);
50 | MethodNode hello = createDummyMethod(ext, "hello", "()V");
51 | hello.access |= ACC_STATIC;
52 |
53 | ClassNode parent = createDummyClass("Parent", OBJECT);
54 | createDummyMethod(parent, "test", "()V");
55 |
56 | ClassNode child = createDummyClass("Child", parent.name);
57 | MethodNode test = createDummyMethod(child, "test", "()V");
58 | test.visitMethodInsn(INVOKESTATIC, ext.name, hello.name, hello.desc, false);
59 |
60 | Workspace workspace = workspaceOf(ext, parent, child);
61 |
62 | Remapper r = new Remapper(workspace, getInfos(workspace, ext, parent, child));
63 | r.mapMethod(new Remapper.MemberID(ext.name, hello.name, Type.getMethodType(hello.desc)), "theReal");
64 | r.mapClass(ext.name, "EXT");
65 | r.mapMethod(new Remapper.MemberID(parent.name, "test", Type.getMethodType(Type.VOID_TYPE)), "fakeMethod");
66 |
67 | r.print();
68 |
69 | List remappedClasses = Stream.of(ext, parent, child)
70 | .map(it -> {
71 | ClassNode rem = new ClassNode();
72 | it.accept(new ClassRemapper(rem, r));
73 | return rem;
74 | })
75 | .toList();
76 | assertEquals("EXT", remappedClasses.get(0).name);
77 | assertEquals("Parent", remappedClasses.get(1).name);
78 | assertEquals("Child", remappedClasses.get(2).name);
79 |
80 | assertEquals("theReal", remappedClasses.get(0).methods.stream().filter(f -> !f.name.startsWith("<")).findFirst().orElseThrow().name);
81 | assertEquals("fakeMethod", remappedClasses.get(1).methods.stream().filter(f -> !f.name.startsWith("<")).findFirst().orElseThrow().name);
82 | MethodNode childFakeMethod = remappedClasses.get(2).methods.stream().filter(f -> !f.name.startsWith("<")).findFirst().orElseThrow();
83 | assertEquals("fakeMethod", childFakeMethod.name);
84 | MethodInsnNode methodInsnNode = (MethodInsnNode) childFakeMethod.instructions.get(0);
85 | assertEquals("EXT", methodInsnNode.owner);
86 | assertEquals("theReal", methodInsnNode.name);
87 | }
88 | }
--------------------------------------------------------------------------------
/core/src/test/resources/createTest.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | NAME="$1"
4 | TP="$2"
5 | if [[ "$TP" == "" ]]; then
6 | TP="ASSEMBLY"
7 | fi
8 | echo "Creating test $NAME"
9 |
10 | mkdir -p "tests/$NAME"
11 |
12 | if [[ "$TP" == "ASSEMBLY" ]]; then
13 | touch "tests/$NAME/$NAME.jasm"
14 | else
15 | cat > "tests/$NAME/$NAME.java" << EOF
16 | public class Test {
17 | public static void main(String[] args) {
18 |
19 | }
20 | }
21 | EOF
22 | fi
23 |
24 | touch "tests/$NAME/$NAME.output"
25 | cat > "tests/$NAME/$NAME.test.json" << EOF
26 | {
27 | "type": "$TP",
28 | "name": "Unnamed Test",
29 | "expectedProgramExit": 0,
30 | "programArgs": null,
31 | "jvmArgs": null
32 | }
33 | EOF
34 | echo "OK"
--------------------------------------------------------------------------------
/core/src/test/resources/junit-platform.properties:
--------------------------------------------------------------------------------
1 | junit.jupiter.execution.parallel.enabled=false
2 | junit.jupiter.execution.parallel.mode.default=concurrent
3 | junit.jupiter.execution.parallel.mode.classes.default=concurrent
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testArrays/testArrays.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class public Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 | getstatic java/lang/System.out Ljava/io/PrintStream;
19 | astore p0
20 | aload p0
21 | bipush 10
22 | anewarray Ljava/lang/String;
23 | dup
24 | iconst_3
25 | ldc "Hello"
26 | aastore
27 | dup
28 | iconst_4
29 | ldc "World"
30 | aastore
31 | dup
32 | iconst_3
33 | aaload
34 | swap
35 | invokestatic java/util/Arrays.toString ([Ljava/lang/Object;)Ljava/lang/String;
36 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
37 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
38 | aload p0
39 | iconst_3
40 | newarray boolean
41 | dup
42 | iconst_1
43 | iconst_1
44 | bastore
45 | invokestatic java/util/Arrays.toString ([Z)Ljava/lang/String;
46 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
47 | aload p0
48 | iconst_3
49 | newarray char
50 | dup
51 | iconst_1
52 | bipush 97
53 | castore
54 | invokevirtual java/io/PrintStream.println ([C)V
55 | aload p0
56 | iconst_3
57 | newarray float
58 | dup
59 | iconst_1
60 | ldc 1.5F
61 | fastore
62 | invokestatic java/util/Arrays.toString ([F)Ljava/lang/String;
63 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
64 | aload p0
65 | iconst_3
66 | newarray double
67 | dup
68 | iconst_1
69 | ldc 1.5D
70 | dastore
71 | invokestatic java/util/Arrays.toString ([D)Ljava/lang/String;
72 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
73 | aload p0
74 | iconst_3
75 | newarray byte
76 | dup
77 | iconst_1
78 | iconst_1
79 | bastore
80 | invokestatic java/util/Arrays.toString ([B)Ljava/lang/String;
81 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
82 | aload p0
83 | iconst_3
84 | newarray short
85 | dup
86 | iconst_1
87 | iconst_1
88 | sastore
89 | invokestatic java/util/Arrays.toString ([S)Ljava/lang/String;
90 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
91 | aload p0
92 | iconst_3
93 | newarray int
94 | dup
95 | iconst_1
96 | iconst_1
97 | iastore
98 | invokestatic java/util/Arrays.toString ([I)Ljava/lang/String;
99 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
100 | aload p0
101 | iconst_3
102 | newarray long
103 | dup
104 | iconst_1
105 | lconst_1
106 | lastore
107 | invokestatic java/util/Arrays.toString ([J)Ljava/lang/String;
108 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
109 | aload p0
110 | iconst_5
111 | iconst_4
112 | iconst_3
113 | multianewarray [[[I 3
114 | dup
115 | iconst_4
116 | aaload
117 | iconst_3
118 | aaload
119 | iconst_2
120 | bipush 123
121 | iastore
122 | invokestatic java/util/Arrays.deepToString ([Ljava/lang/Object;)Ljava/lang/String;
123 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
124 | iconst_m1
125 | newarray int
126 | pop
127 | return
128 | }
129 | }
130 |
131 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testArrays/testArrays.output:
--------------------------------------------------------------------------------
1 | Hello[null, null, null, Hello, World, null, null, null, null, null]
2 | [false, true, false]
3 | \u0000a\u0000
4 | [0.0, 1.5, 0.0]
5 | [0.0, 1.5, 0.0]
6 | [0, 1, 0]
7 | [0, 1, 0]
8 | [0, 1, 0]
9 | [0, 1, 0]
10 | [[[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 123]]]
11 | Exception in thread "main" java.lang.NegativeArraySizeException: -1
12 | at Test.main(Native Method)
13 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testArrays/testArrays.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Arrays",
4 | "expectedProgramExit": 1,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testCasting/testCasting.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class public Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 | ldc "hello world"
19 | astore v1
20 | getstatic java/lang/System.out Ljava/io/PrintStream;
21 | aload v1
22 | instanceof Ljava/lang/String;
23 | invokevirtual java/io/PrintStream.println (Z)V
24 | getstatic java/lang/System.out Ljava/io/PrintStream;
25 | aload v1
26 | instanceof Ljava/lang/CharSequence;
27 | invokevirtual java/io/PrintStream.println (Z)V
28 | getstatic java/lang/System.out Ljava/io/PrintStream;
29 | aload v1
30 | instanceof Ljava/lang/Integer;
31 | invokevirtual java/io/PrintStream.println (Z)V
32 | getstatic java/lang/System.out Ljava/io/PrintStream;
33 | aload v1
34 | checkcast Ljava/lang/String;
35 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
36 | aconst_null
37 | astore v2
38 | getstatic java/lang/System.out Ljava/io/PrintStream;
39 | aload v2
40 | instanceof Ljava/lang/Object;
41 | invokevirtual java/io/PrintStream.println (Z)V
42 | getstatic java/lang/System.out Ljava/io/PrintStream;
43 | aload v2
44 | checkcast Ljava/lang/String;
45 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
46 | return
47 | }
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testCasting/testCasting.output:
--------------------------------------------------------------------------------
1 | true
2 | true
3 | false
4 | hello world
5 | false
6 | null
7 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testCasting/testCasting.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Casting",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantDynamic/testConstantDynamic.jasm:
--------------------------------------------------------------------------------
1 | .super java/lang/Object
2 | .annotation j2cc/Nativeify {}
3 | .class public test {
4 | .method public static main ([Ljava/lang/String;)V {
5 | code: {
6 | getstatic java/lang/System.out Ljava/io/PrintStream;
7 | ldc { I, Ljava/lang/String;, { invokestatic, test.real, (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/String; }, { "hello chat", 123 } }
8 | invokevirtual java/io/PrintStream.println (Ljava/lang/Object;)V
9 |
10 | getstatic java/lang/System.out Ljava/io/PrintStream;
11 | ldc { I, Ljava/lang/String;, { invokestatic, test.real, (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/String; }, { "hello chat", 123 } }
12 | invokevirtual java/io/PrintStream.println (Ljava/lang/Object;)V
13 |
14 | getstatic java/lang/System.out Ljava/io/PrintStream;
15 | ldc { I, Ljava/lang/String;, { invokestatic, test.real, (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/String; }, { "hello chat", 123 } }
16 | invokevirtual java/io/PrintStream.println (Ljava/lang/Object;)V
17 | return
18 | }
19 | }
20 |
21 | .method public static real (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/Class;Ljava/lang/String;I)Ljava/lang/String; {
22 | code: {
23 | ldc "Invoked: "
24 | aload p3
25 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
26 |
27 | iload p4
28 | invokestatic java/lang/String.valueOf (I)Ljava/lang/String;
29 |
30 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
31 |
32 | dup
33 |
34 | getstatic java/lang/System.out Ljava/io/PrintStream;
35 |
36 | swap
37 |
38 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
39 |
40 | ldc "returned value"
41 |
42 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
43 |
44 | areturn
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantDynamic/testConstantDynamic.output:
--------------------------------------------------------------------------------
1 | Invoked: hello chat123
2 | Invoked: hello chat123returned value
3 | Invoked: hello chat123returned value
4 | Invoked: hello chat123returned value
5 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantDynamic/testConstantDynamic.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test ConstantDynamic",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantObf/testConstantObf.java:
--------------------------------------------------------------------------------
1 | public class Test {
2 | public static void main(String[] args) {
3 | System.out.println((int) 1);
4 | System.out.println(1L);
5 | System.out.println(1d);
6 | System.out.println(1f);
7 | System.out.println(Integer.MAX_VALUE);
8 | System.out.println(Integer.MIN_VALUE);
9 | System.out.println(Long.MAX_VALUE);
10 | System.out.println(Long.MIN_VALUE);
11 | System.out.println(Double.MAX_VALUE);
12 | System.out.println(Double.MIN_VALUE);
13 | System.out.println(Float.MIN_VALUE);
14 | System.out.println(Float.MAX_VALUE);
15 | System.out.println("abcd\u0000\u0001\uFFFF\u1234gggggäöü");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantObf/testConstantObf.output:
--------------------------------------------------------------------------------
1 | 1
2 | 1
3 | 1.0
4 | 1.0
5 | 2147483647
6 | -2147483648
7 | 9223372036854775807
8 | -9223372036854775808
9 | 1.7976931348623157E308
10 | 4.9E-324
11 | 1.4E-45
12 | 3.4028235E38
13 | abcd\u0000\u0001\uFFFF\u1234gggggäöü
14 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConstantObf/testConstantObf.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test constant obf",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null,
7 | "obfuscationSettings":{
8 | "constantObfuscation": true
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testControlFlow/testControlFlow.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | public class Test {
4 | private static int echo(int e) {return e;}
5 | @Nativeify
6 | public static void main(String[] args) {
7 | String s = "hello";
8 | if (s.equals("hello")) System.out.println("hello equals hello");
9 | else System.out.println("hello does not equal hello");
10 | if (s == null) System.out.println("hello is null");
11 | else System.out.println("hello is not null");
12 | int i = echo(123);
13 | if (i == 123) System.out.println("123 is 123");
14 | else if (i == 0) System.out.println("123 is 0");
15 | else System.out.println("123 is " + i + ", not 123 or 0");
16 | System.out.println(s != null ? "tenary" : "expression");
17 | switch (s) {
18 | case "hello" -> System.out.println("switch success");
19 | case "no" -> System.out.println("what???");
20 | default -> System.out.println("oh no: " + s);
21 | }
22 | int v = 2;
23 | switch (echo(3*v)) {
24 | case 6:
25 | System.out.println("correct!");
26 | case 4:
27 | System.out.println("also correct");
28 | break;
29 | case 2:
30 | System.out.println("NOT correct!");
31 | break;
32 | case 1:
33 | System.out.println("even less correct");
34 | break;
35 | default:
36 | System.out.println("oh god");
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testControlFlow/testControlFlow.output:
--------------------------------------------------------------------------------
1 | hello equals hello
2 | hello is not null
3 | 123 is 123
4 | tenary
5 | switch success
6 | correct!
7 | also correct
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testControlFlow/testControlFlow.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Control Flow",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConversion/testConversion.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 | getstatic java/lang/System.out Ljava/io/PrintStream;
19 | ldc 10.0D
20 | d2f
21 | invokevirtual java/io/PrintStream.println (F)V
22 | getstatic java/lang/System.out Ljava/io/PrintStream;
23 | ldc 10.4D
24 | d2i
25 | invokevirtual java/io/PrintStream.println (I)V
26 | getstatic java/lang/System.out Ljava/io/PrintStream;
27 | ldc -10.4D
28 | d2i
29 | invokevirtual java/io/PrintStream.println (I)V
30 | getstatic java/lang/System.out Ljava/io/PrintStream;
31 | ldc 10.0D
32 | d2l
33 | invokevirtual java/io/PrintStream.println (J)V
34 | getstatic java/lang/System.out Ljava/io/PrintStream;
35 | ldc 9.5F
36 | f2d
37 | invokevirtual java/io/PrintStream.println (D)V
38 | getstatic java/lang/System.out Ljava/io/PrintStream;
39 | ldc 9.4F
40 | f2i
41 | invokevirtual java/io/PrintStream.println (I)V
42 | getstatic java/lang/System.out Ljava/io/PrintStream;
43 | ldc -9.4F
44 | f2i
45 | invokevirtual java/io/PrintStream.println (I)V
46 | getstatic java/lang/System.out Ljava/io/PrintStream;
47 | ldc -9.4F
48 | f2l
49 | invokevirtual java/io/PrintStream.println (J)V
50 | getstatic java/lang/System.out Ljava/io/PrintStream;
51 | sipush 200
52 | i2b
53 | invokevirtual java/io/PrintStream.println (I)V
54 | getstatic java/lang/System.out Ljava/io/PrintStream;
55 | bipush 93
56 | i2c
57 | invokevirtual java/io/PrintStream.println (C)V
58 | getstatic java/lang/System.out Ljava/io/PrintStream;
59 | bipush 123
60 | i2d
61 | invokevirtual java/io/PrintStream.println (D)V
62 | getstatic java/lang/System.out Ljava/io/PrintStream;
63 | bipush 123
64 | i2f
65 | invokevirtual java/io/PrintStream.println (F)V
66 | getstatic java/lang/System.out Ljava/io/PrintStream;
67 | bipush 123
68 | i2l
69 | invokevirtual java/io/PrintStream.println (J)V
70 | getstatic java/lang/System.out Ljava/io/PrintStream;
71 | ldc 90000
72 | i2s
73 | invokevirtual java/io/PrintStream.println (I)V
74 | getstatic java/lang/System.out Ljava/io/PrintStream;
75 | ldc 123L
76 | l2d
77 | invokevirtual java/io/PrintStream.println (D)V
78 | getstatic java/lang/System.out Ljava/io/PrintStream;
79 | ldc 123L
80 | l2f
81 | invokevirtual java/io/PrintStream.println (F)V
82 | getstatic java/lang/System.out Ljava/io/PrintStream;
83 | ldc 5473857893L
84 | l2i
85 | invokevirtual java/io/PrintStream.println (I)V
86 | return
87 | }
88 | }
89 |
90 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConversion/testConversion.output:
--------------------------------------------------------------------------------
1 | 10.0
2 | 10
3 | -10
4 | 10
5 | 9.5
6 | 9
7 | -9
8 | -9
9 | -56
10 | ]
11 | 123.0
12 | 123.0
13 | 123
14 | 24464
15 | 123.0
16 | 123.0
17 | 1178890597
18 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testConversion/testConversion.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Conversion",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testDups/testDups.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class public Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 |
19 | iconst_0 // ps, ps, 0
20 | dup
21 | getstatic java/lang/System.out Ljava/io/PrintStream;
22 | swap
23 | invokevirtual java/io/PrintStream.println (I)V
24 |
25 | getstatic java/lang/System.out Ljava/io/PrintStream;
26 | swap
27 | invokevirtual java/io/PrintStream.println (I)V
28 |
29 | getstatic java/lang/System.out Ljava/io/PrintStream;
30 | dup // ps, ps
31 | iconst_0 // ps, ps, 0
32 | dup_x1 // ps, 0, ps, 0
33 | invokevirtual java/io/PrintStream.println (I)V
34 | invokevirtual java/io/PrintStream.println (I)V
35 | // empty
36 |
37 | getstatic java/lang/System.out Ljava/io/PrintStream;
38 | dup // ps, ps
39 |
40 | lconst_1
41 | dup2_x1 // ps, l, ps, l
42 |
43 | invokevirtual java/io/PrintStream.println (J)V
44 | invokevirtual java/io/PrintStream.println (J)V
45 |
46 | // EMPTY
47 |
48 | getstatic java/lang/System.out Ljava/io/PrintStream;
49 | dup // ps, ps
50 |
51 | iconst_0 // ps, ps, i
52 |
53 | lconst_1 // ps, ps, i, l
54 | dup2_x2 // ps, l, ps, i, l
55 | pop2 // ps, l, ps, i
56 | pop2 // ps, l
57 | invokevirtual java/io/PrintStream.println (J)V
58 |
59 | // EMPTY
60 |
61 | getstatic java/lang/System.out Ljava/io/PrintStream;
62 | // ps
63 |
64 | lconst_0 // ps, l
65 | dup2 // ps, l, l
66 | iconst_1 // ps, l, l, i
67 | dup // ps, l, l, i, i
68 | dup2_x2 // ps, l, i, i, l, i, i
69 | pop2 // ps, l, i, i, l
70 | pop2 // ps, l, i, i
71 |
72 | iadd // ps, l, i+i
73 | i2l // ps, l+i+i
74 |
75 | ladd
76 |
77 | invokevirtual java/io/PrintStream.println (J)V // should be 2
78 |
79 | return
80 | }
81 | }
82 |
83 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testDups/testDups.output:
--------------------------------------------------------------------------------
1 | 0
2 | 0
3 | 0
4 | 0
5 | 1
6 | 1
7 | 1
8 | 2
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testDups/testDups.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Dups",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFields/testFields.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | public class Test {
4 | private static String setMe = "hello world";
5 | private String setMe1 = "hello chat";
6 |
7 | @Nativeify
8 | public static void main(String[] args) {
9 | System.out.println(setMe);
10 | System.out.println(setMe = "hello");
11 | System.out.println(setMe);
12 |
13 | Test test = new Test();
14 | Test test2 = new Test();
15 | System.out.println(test.setMe1);
16 | System.out.println(test2.setMe1);
17 | test.setMe1 = "hello!";
18 | System.out.println(test.setMe1);
19 | System.out.println(test2.setMe1);
20 | }
21 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFields/testFields.output:
--------------------------------------------------------------------------------
1 | hello world
2 | hello
3 | hello
4 | hello chat
5 | hello chat
6 | hello!
7 | hello chat
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFields/testFields.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Fields",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFinally/testFinally.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | @Nativeify
4 | public class Test {
5 | static int real() {
6 | try {
7 | System.out.println("returning in real");
8 | return 123;
9 | } finally {
10 | System.out.println("finally executed");
11 | }
12 | }
13 |
14 | public static void main(String[] args) {
15 | try {
16 | System.out.println("abc");
17 | throw new RuntimeException("abc");
18 | } catch (Throwable t) {
19 | t.printStackTrace();
20 | } finally {
21 | System.out.println("finally");
22 | }
23 | System.out.println(real());
24 | }
25 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFinally/testFinally.output:
--------------------------------------------------------------------------------
1 | abc
2 | java.lang.RuntimeException: abc
3 | at Test.main(Native Method)
4 | finally
5 | returning in real
6 | finally executed
7 | 123
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testFinally/testFinally.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Finally",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testHandleLdc/testHandleLdc.output:
--------------------------------------------------------------------------------
1 | MethodHandle()Class
2 | int
3 | MethodHandle()Test
4 | yep, this is a Test instance. real is: 0
5 | MethodHandle(Test,int)void
6 | MethodHandle(Test)int
7 | 123
8 | MethodHandle(int)void
9 | MethodHandle()int
10 | 121
11 | MethodHandle(Object)void
12 | hi
13 | MethodHandle(Test,String)void
14 | real is at: 123
15 | Exception in thread "main" java.lang.IllegalAccessError: unexpected set of a final field: java.lang.String.value/[B/putField, from class Test (unnamed module @\?)
16 | at java.base/java.lang.invoke.MethodHandleNatives.mapLookupExceptionToError(MethodHandleNatives.java:\?)
17 | at java.base/java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:\?)
18 | at Test.main(Native Method)
19 | Caused by: java.lang.IllegalAccessException: unexpected set of a final field: java.lang.String.value/[B/putField, from class Test (unnamed module @\?)
20 | at java.base/java.lang.invoke.MemberName.makeAccessException(MemberName.java:\?)
21 | at java.base/java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:\?)
22 | at java.base/java.lang.invoke.MethodHandles$Lookup.checkField(MethodHandles.java:\?)
23 | at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldCommon(MethodHandles.java:\?)
24 | at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectFieldNoSecurityManager(MethodHandles.java:\?)
25 | at java.base/java.lang.invoke.MethodHandles$Lookup.getDirectMethodForConstant(MethodHandles.java:\?)
26 | at java.base/java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(MethodHandles.java:\?)
27 | at java.base/java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(MethodHandleNatives.java:\?)
28 | ... 1 more
29 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testHandleLdc/testHandleLdc.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Handle LDC",
4 | "expectedProgramExit": 1,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testHelloWorld/testHelloWorld.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 | getstatic java/lang/System.out Ljava/io/PrintStream;
19 | ldc "Hello chat, "
20 | ldc "Hi!"
21 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
22 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
23 | return
24 | }
25 | }
26 |
27 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testHelloWorld/testHelloWorld.output:
--------------------------------------------------------------------------------
1 | Hello chat, Hi!
2 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testHelloWorld/testHelloWorld.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Hello World",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLambdas/testLambdas.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | import java.util.function.BiFunction;
4 | import java.util.function.Consumer;
5 |
6 | @Nativeify
7 | public class Test {
8 | private static void runLambda(Runnable r) {
9 | r.run();
10 | }
11 |
12 | public static void main(String[] args) {
13 | String s = "Hello world";
14 | runLambda(() -> {
15 | System.out.println(s);
16 | });
17 | Consumer printer = v -> System.out.println(v);
18 | printer.accept("Hello chat");
19 |
20 | BiFunction stringConcatenator = (a, b) -> a + b;
21 | printer.accept(stringConcatenator.apply("Hello", "World"));
22 | }
23 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLambdas/testLambdas.output:
--------------------------------------------------------------------------------
1 | Hello world
2 | Hello chat
3 | HelloWorld
4 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLambdas/testLambdas.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Lambdas",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLocals/testLocals.jasm:
--------------------------------------------------------------------------------
1 | .annotation j2cc/Nativeify {}
2 | .super java/lang/Object
3 | .class public Test {
4 |
5 |
6 | .method ()V {
7 | parameters: { this },
8 | code: {
9 | aload this
10 | invokespecial java/lang/Object. ()V
11 | return
12 | }
13 | }
14 |
15 | .method public static main ([Ljava/lang/String;)V {
16 | parameters: { p0 },
17 | code: {
18 | aload p0
19 | dup
20 | iconst_0
21 | aaload
22 | astore v1
23 | iconst_1
24 | aaload
25 | astore v2
26 | ldc 543789L
27 | lstore v3
28 | aconst_null
29 | astore v5
30 | getstatic java/lang/System.out Ljava/io/PrintStream;
31 | dup
32 | dup
33 | aload v1
34 | aload v2
35 | invokevirtual java/lang/String.concat (Ljava/lang/String;)Ljava/lang/String;
36 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
37 | aload v5
38 | invokevirtual java/io/PrintStream.println (Ljava/lang/String;)V
39 | lload v3
40 | invokevirtual java/io/PrintStream.println (J)V
41 | ldc 5L
42 | lload v3
43 | ladd
44 | lstore v3
45 | getstatic java/lang/System.out Ljava/io/PrintStream;
46 | lload v3
47 | invokevirtual java/io/PrintStream.println (J)V
48 | return
49 | }
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLocals/testLocals.output:
--------------------------------------------------------------------------------
1 | hellochat
2 | null
3 | 543789
4 | 543794
5 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLocals/testLocals.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Locals",
4 | "expectedProgramExit": 0,
5 | "programArgs": [
6 | "hello",
7 | "chat"
8 | ],
9 | "jvmArgs": null
10 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLoops/testLoops.java:
--------------------------------------------------------------------------------
1 | @j2cc.Nativeify
2 | public class Test {
3 | public static void main(String[] args) {
4 | int i;
5 | for (i = 0; i < 10; i++) {
6 | System.out.printf("i = %d%n", i);
7 | }
8 | System.out.printf("end: i = %d%n", i);
9 | while (i > 0) {
10 | System.out.printf("i = %d%n", i--);
11 | }
12 | System.out.printf("end: i = %d%n", i);
13 | for (int v = 0; v < 60; v++) {
14 | if (v % 5 != 0) continue;
15 | if (v % 3 == 0) continue;
16 | System.out.printf("v = %d%n", v);
17 | }
18 | out:
19 | for (int e = 1; e < 8; e++) {
20 | for (int v = 1; v < e; v++) {
21 | if (v % 5 == 0) continue out;
22 | System.out.printf("%d %d%n", e, v);
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLoops/testLoops.output:
--------------------------------------------------------------------------------
1 | i = 0
2 | i = 1
3 | i = 2
4 | i = 3
5 | i = 4
6 | i = 5
7 | i = 6
8 | i = 7
9 | i = 8
10 | i = 9
11 | end: i = 10
12 | i = 10
13 | i = 9
14 | i = 8
15 | i = 7
16 | i = 6
17 | i = 5
18 | i = 4
19 | i = 3
20 | i = 2
21 | i = 1
22 | end: i = 0
23 | v = 5
24 | v = 10
25 | v = 20
26 | v = 25
27 | v = 35
28 | v = 40
29 | v = 50
30 | v = 55
31 | 2 1
32 | 3 1
33 | 3 2
34 | 4 1
35 | 4 2
36 | 4 3
37 | 5 1
38 | 5 2
39 | 5 3
40 | 5 4
41 | 6 1
42 | 6 2
43 | 6 3
44 | 6 4
45 | 7 1
46 | 7 2
47 | 7 3
48 | 7 4
49 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testLoops/testLoops.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Loops",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testMath/testMath.output:
--------------------------------------------------------------------------------
1 | 5.8999999999999995
2 | 4.7
3 | -8.833333333333334
4 | 8.833333333333334
5 | Infinity
6 | -3.1799999999999997
7 | 3.1799999999999997
8 | -5.3
9 | 1.9
10 | 4.7000003
11 | -8.833333
12 | 8.833333
13 | Infinity
14 | -3.1800003
15 | 3.1800003
16 | -5.3
17 | 1.9000001
18 | 8
19 | 2
20 | -2
21 | 2
22 | 3
23 | -10
24 | 10
25 | -5
26 | 2
27 | 1
28 | 7
29 | 40
30 | 62
31 | -13
32 | 536870899
33 | 86
34 | 8
35 | 2
36 | -2
37 | 2
38 | 3
39 | -10
40 | 10
41 | -5
42 | 2
43 | 1
44 | 7
45 | 40
46 | 62
47 | -13
48 | 2305843009213693939
49 | 86
50 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testMath/testMath.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "ASSEMBLY",
3 | "name": "Test Math",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testMethodHandles/testMethodHandles.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | import java.lang.invoke.MethodHandle;
4 | import java.lang.invoke.MethodHandles;
5 | import java.lang.invoke.MethodType;
6 | import java.lang.invoke.VarHandle;
7 |
8 | public class Test {
9 | String someField = "Cringe";
10 |
11 | @Nativeify
12 | public static void main(String[] args) throws Throwable {
13 | MethodHandle format = MethodHandles.lookup().findStatic(String.class, "format", MethodType.methodType(String.class, String.class, Object[].class));
14 | System.out.println(format.invoke("Hello %s! I am %d years old", "World", 1234));
15 | Object[] o = new Object[]{"Hello %s! I am %d years old", "World", 1234};
16 | System.out.println(format.invokeWithArguments(o));
17 | System.out.println(format.asFixedArity().invoke("Hello %s! I am %d years old", new Object[]{"World", 1234}));
18 | System.out.println(((String) format.invokeWithArguments("Hello %s", o)).split("@")[0]);
19 |
20 | Test inst = new Test();
21 | System.out.println(inst.someField);
22 | VarHandle varHandle = MethodHandles.lookup().findVarHandle(Test.class, "someField", String.class);
23 | System.out.println(varHandle.get(inst));
24 | varHandle.set(inst, "Yep");
25 | System.out.println(inst.someField);
26 | System.out.println(varHandle.get(inst));
27 |
28 | }
29 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testMethodHandles/testMethodHandles.output:
--------------------------------------------------------------------------------
1 | Hello World! I am 1234 years old
2 | Hello World! I am 1234 years old
3 | Hello World! I am 1234 years old
4 | Hello [Ljava.lang.Object;
5 | Cringe
6 | Cringe
7 | Yep
8 | Yep
9 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testMethodHandles/testMethodHandles.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Handles",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testObfuscation/testObfuscation.java:
--------------------------------------------------------------------------------
1 | import j2cc.AlwaysInline;
2 | import j2cc.Nativeify;
3 |
4 | @Nativeify
5 | public class Test {
6 | String msg;
7 |
8 | public Test(String msg) {
9 | this.msg = msg;
10 | }
11 |
12 | @AlwaysInline
13 | private static void printStacktrace() {
14 | System.out.println("We're currently in:");
15 | new Throwable().printStackTrace();
16 | }
17 |
18 | @AlwaysInline
19 | private static String test(String s) {
20 | return "hello " + s;
21 | }
22 |
23 | // @Nativeify
24 | public static void main(String[] args) {
25 | System.out.printf("hello %s%n", "varargs");
26 | System.out.println("hello simple print");
27 |
28 | System.out.println(test("inlining"));
29 |
30 | printStacktrace();
31 |
32 | new Test("hello world").pri();
33 |
34 | System.out.println(Integer.valueOf(0));
35 | System.out.println(Integer.valueOf(0) == Integer.valueOf(0));
36 | System.out.println(Integer.valueOf(-128) == Integer.valueOf(-128));
37 | System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
38 | System.out.println(Integer.valueOf(-129) == Integer.valueOf(-129));
39 | System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
40 |
41 | System.out.println(Character.valueOf((char) 127) == Character.valueOf((char) 127));
42 | System.out.println(Character.valueOf((char) 128) == Character.valueOf((char) 128));
43 | System.out.println(Character.valueOf((char) 123));
44 | }
45 |
46 | @AlwaysInline
47 | void pri() {
48 | printStacktrace();
49 | System.out.println("Yep: " + msg);
50 | }
51 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testObfuscation/testObfuscation.output:
--------------------------------------------------------------------------------
1 | hello varargs
2 | hello simple print
3 | hello inlining
4 | We're currently in:
5 | java.lang.Throwable
6 | at Test.main(Native Method)
7 | We're currently in:
8 | java.lang.Throwable
9 | at Test.main(Native Method)
10 | Yep: hello world
11 | 0
12 | true
13 | true
14 | true
15 | false
16 | false
17 | true
18 | false
19 | {
20 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testObfuscation/testObfuscation.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Obfuscation",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null,
7 | "obfuscationSettings": {
8 | "opaqueCalls": {
9 | "enable": true,
10 | "includeNative": true
11 | },
12 | "inliner": {
13 | "enable": true,
14 | "maxSize": 64
15 | },
16 | "removeUnusedClasses": {
17 | "enable": true,
18 | "extraEntryClasses": {
19 | "extraEntryClass": [
20 | "Test"
21 | ]
22 | }
23 | },
24 | "flowSettings": {
25 | "enable": true,
26 | "strategy": "FLATTEN"
27 | },
28 | "outlineMethods": {
29 | "enabled": true,
30 | "minInstructions": 2
31 | },
32 | "removeDebugInfo": true
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testPropagatingExceptions/testPropagatingExceptions.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | @Nativeify
4 | public class Test {
5 | private static void thisThrows() {
6 | throw new RuntimeException("hello chat");
7 | }
8 |
9 | private static void doesNotCatch() {
10 | System.out.println("this should print");
11 | thisThrows();
12 | System.out.println("this should not be printed");
13 | }
14 |
15 | private static void doesCatch() {
16 | System.out.println("this should print");
17 | try {
18 | thisThrows();
19 | } catch (RuntimeException re) {
20 | re.printStackTrace();
21 | }
22 | System.out.println("this should print as well (after catch)");
23 | }
24 |
25 | public static void main(String[] args) {
26 | doesCatch();
27 | doesNotCatch();
28 | System.out.println("should not print");
29 | }
30 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testPropagatingExceptions/testPropagatingExceptions.output:
--------------------------------------------------------------------------------
1 | this should print
2 | java.lang.RuntimeException: hello chat
3 | at Test.thisThrows(Native Method)
4 | at Test.doesCatch(Native Method)
5 | at Test.main(Native Method)
6 | this should print as well (after catch)
7 | this should print
8 | Exception in thread "main" java.lang.RuntimeException: hello chat
9 | at Test.thisThrows(Native Method)
10 | at Test.doesNotCatch(Native Method)
11 | at Test.main(Native Method)
12 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testPropagatingExceptions/testPropagatingExceptions.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Propagating Exceptions",
4 | "expectedProgramExit": 1,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testReturnInFinally/testReturnInFinally.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | @Nativeify
4 | public class Test {
5 | static String what() {
6 | try {
7 | System.out.println("in try");
8 | return "hi";
9 | } finally {
10 | System.out.println("in finally");
11 | return "what";
12 | }
13 | }
14 |
15 | public static void main(String[] args) {
16 | System.out.println(what());
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testReturnInFinally/testReturnInFinally.output:
--------------------------------------------------------------------------------
1 | in try
2 | in finally
3 | what
4 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testReturnInFinally/testReturnInFinally.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Return in Finally",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testStringOps/testStringOps.java:
--------------------------------------------------------------------------------
1 | @j2cc.Nativeify
2 | public class Test {
3 | public static void main(String[] args) {
4 | String s = "hello chat";
5 | System.out.println(s);
6 | System.out.println(s.length());
7 | System.out.println(s.isEmpty());
8 | System.out.println(s.equals("goodbye chat"));
9 |
10 | System.out.println("".length());
11 | System.out.println(" ".length());
12 |
13 | System.out.println("".isEmpty());
14 | System.out.println(" ".isEmpty());
15 |
16 | System.out.println("".equals(" "));
17 |
18 | System.out.println("\u9986äöü\uFFFF\uFFFE\u0000\u48FEhello world");
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testStringOps/testStringOps.output:
--------------------------------------------------------------------------------
1 | hello chat
2 | 10
3 | false
4 | false
5 | 0
6 | 1
7 | true
8 | false
9 | false
10 | \u9986äöü\uFFFF\uFFFE\u0000\u48FEhello world
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testStringOps/testStringOps.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Strings",
4 | "expectedProgramExit": 0,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testTryCatch/testTryCatch.java:
--------------------------------------------------------------------------------
1 | import j2cc.Nativeify;
2 |
3 | public class Test {
4 | @Nativeify
5 | public static void main(String[] args) {
6 | try {
7 | throw new RuntimeException("Test 123");
8 | } catch (ArithmeticException a) {
9 | System.out.println("arithmetic (wrong): " + a.getMessage());
10 | } catch (RuntimeException re) {
11 | System.out.println("runtime exception (correct): " + re.getMessage());
12 | }
13 |
14 | try {
15 | int i = 1 / 0;
16 | } catch (ArithmeticException a) {
17 | System.out.println("arithmetic (correct): " + a.getMessage());
18 | } catch (RuntimeException re) {
19 | System.out.println("runtime exception (wrong): " + re.getMessage());
20 | }
21 |
22 | try {
23 | try {
24 | int i = 1 / 0;
25 | } catch (RuntimeException re) {
26 | System.out.println("inner (correct): " + re.getMessage());
27 | throw re;
28 | }
29 | } catch (ArithmeticException e) {
30 | System.out.println("outer (also correct): " + e.getMessage());
31 | }
32 |
33 | try {
34 | System.out.println("try body does not throw (correct)");
35 | } finally {
36 | System.out.println("finally still runs (correct)");
37 | }
38 |
39 | try {
40 | try {
41 | int i = 1 / 0;
42 | } catch (RuntimeException re) {
43 | System.out.println("inner RuntimeException (correct): " + re.getMessage());
44 | throw re;
45 | }
46 | } catch (IllegalArgumentException e) {
47 | System.out.println("outer IllegalArgument (wrong): " + e.getMessage());
48 | } finally {
49 | System.out.println("finally executed (correct)");
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testTryCatch/testTryCatch.output:
--------------------------------------------------------------------------------
1 | runtime exception (correct): Test 123
2 | arithmetic (correct): / by zero
3 | inner (correct): / by zero
4 | outer (also correct): / by zero
5 | try body does not throw (correct)
6 | finally still runs (correct)
7 | inner RuntimeException (correct): / by zero
8 | finally executed (correct)
9 | Exception in thread "main" java.lang.ArithmeticException: / by zero
10 | at Test.main(Native Method)
11 |
--------------------------------------------------------------------------------
/core/src/test/resources/tests/testTryCatch/testTryCatch.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "SOURCE",
3 | "name": "Test Try Catch",
4 | "expectedProgramExit": 1,
5 | "programArgs": null,
6 | "jvmArgs": null
7 | }
8 |
--------------------------------------------------------------------------------
/createDist.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | CDIR="core/target/dist-core"
3 | #PLUGIN_F=$(find j2cc-maven-plugin/target/ -maxdepth 1 -mindepth 1 -type f -name "j2cc-maven-plugin-*.jar")
4 |
5 | rm -rfv dist_package dist.tar.gz
6 | mkdir dist_package
7 | cp -r "$CDIR"/* "dist_package/"
8 | #cp "$CORE_F" dist_package
9 | #cp "$PLUGIN_F" dist_package
10 | cp -r util dist_package
11 | cp -r natives dist_package
12 | rm -rv dist_package/util/.idea
13 | rm -rv dist_package/util/cmake-build-*
14 | rm -v dist_package/util/CMakeLists.txt
15 | tar cvf dist.tar.gz dist_package
--------------------------------------------------------------------------------
/internals/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.x150.j2cc
8 | j2cc-bom
9 | 1.0-SNAPSHOT
10 |
11 |
12 | ${j2cc.version}
13 | internals
14 |
15 |
16 |
17 | org.projectlombok
18 | lombok
19 | compile
20 |
21 |
22 |
23 |
24 | 8
25 | 8
26 | UTF-8
27 |
28 |
29 |
--------------------------------------------------------------------------------
/internals/src/main/java/j2cc/internal/Debug.java:
--------------------------------------------------------------------------------
1 | package j2cc.internal;
2 |
3 | import java.lang.annotation.Retention;
4 | import java.lang.annotation.RetentionPolicy;
5 |
6 | @Retention(RetentionPolicy.CLASS)
7 | @Debug
8 | public @interface Debug {
9 | }
10 |
--------------------------------------------------------------------------------
/internals/src/main/java/j2cc/internal/Loader.java:
--------------------------------------------------------------------------------
1 | package j2cc.internal;
2 |
3 | import java.io.*;
4 | import java.nio.file.Files;
5 | import java.nio.file.Path;
6 | import java.util.Objects;
7 | import java.util.concurrent.atomic.AtomicBoolean;
8 |
9 | /**
10 | * This class is a template for the actual loader initializer. It serves no functionality in the original jar
11 | */
12 | public class Loader {
13 | private static final AtomicBoolean loaded = new AtomicBoolean(false);
14 |
15 | @Debug
16 | private static void printDebug1(String a, String b) {
17 | System.out.printf(a + "%n", b);
18 | }
19 |
20 | public static void init() {
21 | // v.cAS(a, b): if v == a { v := b; return true; } else { return false; }
22 | if (!loaded.compareAndSet(false, true)) return;
23 | String resourcePrefix = Platform.RESOURCE_PREFIX;
24 | printDebug1("j2cc loader v1.0.1%nPlatform: %s", resourcePrefix);
25 | ClassLoader cl = Loader.class.getClassLoader();
26 | try (InputStream resourceAsStream = cl.getResourceAsStream("j2cc/relocationInfo.dat");
27 | DataInputStream dis = new DataInputStream(Objects.requireNonNull(resourceAsStream));
28 | InputStream natives = Objects.requireNonNull(cl.getResourceAsStream("j2cc/natives.bin"))) {
29 | while (!dis.readUTF().equals(resourcePrefix)) {
30 | dis.skipBytes(16+48);
31 | }
32 | long pos = dis.readLong();
33 | long fileLength = dis.readLong();
34 | byte[] k = new byte[48];
35 | dis.readFully(k);
36 | long total = 0;
37 | long cur;
38 | while (total < pos && (cur = (int) natives.skip(pos - total)) > 0) {
39 | total += cur;
40 | }
41 | if (total != pos) throw new IOException("Failed to read natives file");
42 | Path tempFile = Files.createTempFile("j2cc", Platform.osType == Platform.WINDOWS ? ".dll" : "");
43 | tempFile.toFile().deleteOnExit();
44 | try (OutputStream outputStream = Files.newOutputStream(tempFile)) {
45 | long totalRead = 0;
46 | final int maxSize = 1024 * 8;
47 | byte[] buffer = new byte[maxSize];
48 | while (totalRead < fileLength) {
49 | long rem = fileLength - totalRead;
50 | int size = (int) Math.min(maxSize, rem);
51 | int actuallyRead = natives.read(buffer, 0, size);
52 | outputStream.write(buffer, 0, actuallyRead);
53 | totalRead += actuallyRead;
54 | }
55 | }
56 |
57 | String string = tempFile.toString();
58 | printDebug1("Loading library from %s...", string);
59 | System.load(string);
60 | bootstrap(k);
61 | } catch (EOFException ee) {
62 | System.err.println("Your platform (" + resourcePrefix + ") isn't supported by this application. Please contact the developers to find out more.");
63 | System.exit(127);
64 | } catch (IOException e) {
65 | throw new Error(e);
66 | }
67 | }
68 |
69 | private static native void bootstrap(byte[] k);
70 |
71 | private static native void initClass(Class> cl);
72 |
73 | public static void doInit(Class> cl) {
74 | init();
75 | initClass(cl);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/internals/src/main/java/j2cc/internal/Platform.java:
--------------------------------------------------------------------------------
1 | package j2cc.internal;
2 |
3 | /**
4 | * Stripped down and modified version of JNA's Platform class
5 | *
6 | * @see jna/.../Platform
7 | */
8 | public final class Platform {
9 | public static final int UNSPECIFIED = -1;
10 | public static final int MAC = 0;
11 | public static final int LINUX = 1;
12 | public static final int WINDOWS = 2;
13 | public static final int SOLARIS = 3;
14 | public static final int FREEBSD = 4;
15 | public static final int OPENBSD = 5;
16 | public static final int WINDOWSCE = 6;
17 | public static final int AIX = 7;
18 | public static final int ANDROID = 8;
19 | public static final int GNU = 9;
20 | public static final int KFREEBSD = 10;
21 | public static final int NETBSD = 11;
22 |
23 | public static final String RESOURCE_PREFIX;
24 | public static final int osType;
25 | public static final String ARCH;
26 | public static final String JNI_INCLUDE;
27 |
28 | static {
29 | String osName = System.getProperty("os.name");
30 | if (osName.startsWith("Linux")) {
31 | if ("dalvik".equalsIgnoreCase(System.getProperty("java.vm.name"))) {
32 | osType = ANDROID;
33 | } else {
34 | osType = LINUX;
35 | }
36 | JNI_INCLUDE = "linux";
37 | } else if (osName.startsWith("AIX")) {
38 | osType = AIX;
39 | JNI_INCLUDE = "linux";
40 | } else if (osName.startsWith("Mac") || osName.startsWith("Darwin")) {
41 | osType = MAC;
42 | JNI_INCLUDE = "darwin";
43 | } else if (osName.startsWith("Windows CE")) {
44 | osType = WINDOWSCE;
45 | JNI_INCLUDE = "win32";
46 | } else if (osName.startsWith("Windows")) {
47 | osType = WINDOWS;
48 | JNI_INCLUDE = "win32";
49 | } else if (osName.startsWith("Solaris") || osName.startsWith("SunOS")) {
50 | osType = SOLARIS;
51 | JNI_INCLUDE = "linux";
52 | } else if (osName.startsWith("FreeBSD")) {
53 | osType = FREEBSD;
54 | JNI_INCLUDE = "linux";
55 | } else if (osName.startsWith("OpenBSD")) {
56 | osType = OPENBSD;
57 | JNI_INCLUDE = "linux";
58 | } else if (osName.equalsIgnoreCase("gnu")) {
59 | osType = GNU;
60 | JNI_INCLUDE = "linux";
61 | } else if (osName.equalsIgnoreCase("gnu/kfreebsd")) {
62 | osType = KFREEBSD;
63 | JNI_INCLUDE = "linux";
64 | } else if (osName.equalsIgnoreCase("netbsd")) {
65 | osType = NETBSD;
66 | JNI_INCLUDE = "linux";
67 | } else {
68 | osType = UNSPECIFIED;
69 | JNI_INCLUDE = "linux";
70 | }
71 | ARCH = getCanonicalArchitecture(System.getProperty("os.arch"));
72 | RESOURCE_PREFIX = getNativeLibraryResourcePrefix();
73 | }
74 |
75 | private Platform() {
76 | }
77 |
78 | static String getCanonicalArchitecture(String arch) {
79 | arch = arch.toLowerCase().trim();
80 | switch (arch) {
81 | case "i386":
82 | case "i686":
83 | arch = "x86";
84 | break;
85 | case "x86_64":
86 | case "amd64":
87 | arch = "x86_64";
88 | break;
89 | case "zarch_64":
90 | arch = "s390x";
91 | break;
92 | }
93 | // Work around OpenJDK mis-reporting os.arch
94 | // https://bugs.openjdk.java.net/browse/JDK-8073139
95 | if ("powerpc64".equals(arch) && "little".equals(System.getProperty("sun.cpu.endian"))) {
96 | arch = "powerpc64le";
97 | }
98 | return arch;
99 | }
100 |
101 | static String getNativeLibraryResourcePrefix() {
102 | return getNativeLibraryResourcePrefix(osType, System.getProperty("os.arch"), System.getProperty("os.name"));
103 | }
104 |
105 | static String getNativeLibraryResourcePrefix(int osType, String arch, String name) {
106 | String osPrefix;
107 | arch = getCanonicalArchitecture(arch);
108 | switch (osType) {
109 | case Platform.ANDROID:
110 | if (arch.startsWith("arm")) {
111 | arch = "arm";
112 | }
113 | osPrefix = arch + "-android";
114 | break;
115 | case Platform.LINUX:
116 | osPrefix = arch + "-linux";
117 | break;
118 | case Platform.WINDOWS:
119 | case Platform.WINDOWSCE:
120 | osPrefix = arch + "-windows";
121 | break;
122 | case Platform.MAC:
123 | osPrefix = arch + "-macos";
124 | break;
125 | case Platform.SOLARIS:
126 | osPrefix = arch + "-sunos";
127 | break;
128 | case Platform.FREEBSD:
129 | osPrefix = arch + "-freebsd";
130 | break;
131 | case Platform.OPENBSD:
132 | osPrefix = arch + "-openbsd";
133 | break;
134 | case Platform.NETBSD:
135 | osPrefix = arch + "-netbsd";
136 | break;
137 | case Platform.KFREEBSD:
138 | osPrefix = arch + "-kfreebsd";
139 | break;
140 | default:
141 | osPrefix = name.toLowerCase();
142 | int space = osPrefix.indexOf(" ");
143 | if (space != -1) {
144 | osPrefix = osPrefix.substring(0, space);
145 | }
146 | osPrefix = arch + "-" + osPrefix;
147 | break;
148 | }
149 | return osPrefix;
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/j2cc-maven-plugin/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | me.x150.j2cc
6 | j2cc-bom
7 | 1.0-SNAPSHOT
8 |
9 |
10 | j2cc-maven-plugin
11 | maven-plugin
12 |
13 | ${j2cc.version}
14 | j2cc Maven Plugin
15 |
16 |
17 | UTF-8
18 | 1.2.0
19 |
20 |
21 |
22 |
23 | org.apache.maven
24 | maven-plugin-api
25 | 3.9.6
26 | provided
27 |
28 |
29 |
30 |
31 | org.apache.maven.plugin-tools
32 | maven-plugin-annotations
33 | 3.10.1
34 | provided
35 |
36 |
37 |
38 | me.x150.j2cc
39 | core
40 | ${j2cc.version}
41 |
42 |
43 | me.x150.j2cc
44 | annotations
45 |
46 |
47 |
48 |
49 |
50 | org.apache.maven
51 | maven-project
52 | 2.2.1
53 | provided
54 |
55 |
56 | org.apache.maven
57 | maven-core
58 | 3.9.6
59 | provided
60 |
61 |
62 |
63 | org.codehaus.plexus
64 | plexus-java
65 | ${plexus-java.version}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | org.apache.maven.plugins
74 | maven-plugin-plugin
75 | 3.9.0
76 |
77 | j2cc
78 |
79 |
80 |
81 | help-mojo
82 |
83 | helpmojo
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | apache
93 | https://repo.maven.apache.org/maven2/
94 |
95 |
96 |
97 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 |
8 | 21
9 | 21
10 | UTF-8
11 | 1.0.8
12 | 9.7.1
13 |
14 | me.x150.j2cc
15 | j2cc-bom
16 | 1.0-SNAPSHOT
17 | pom
18 |
19 | testjar
20 | core
21 | annotations
22 |
23 |
24 | internals
25 |
26 |
27 |
28 |
29 |
30 | org.junit
31 | junit-bom
32 | 5.11.0
33 | pom
34 | import
35 |
36 |
37 | org.ow2.asm
38 | asm
39 | ${asm.version}
40 |
41 |
42 | org.ow2.asm
43 | asm-tree
44 | ${asm.version}
45 |
46 |
47 | org.ow2.asm
48 | asm-analysis
49 | ${asm.version}
50 |
51 |
52 | org.ow2.asm
53 | asm-commons
54 | ${asm.version}
55 |
56 |
57 | org.ow2.asm
58 | asm-util
59 | ${asm.version}
60 |
61 |
62 | org.projectlombok
63 | lombok
64 | 1.18.34
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/testjar/dependency-reduced-pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | j2cc-bom
5 | me.x150.j2cc
6 | 1.0-SNAPSHOT
7 |
8 | 4.0.0
9 | testjar
10 | ${j2cc.version}
11 |
12 |
13 |
14 | maven-shade-plugin
15 | 3.4.1
16 |
17 |
18 | package
19 |
20 | shade
21 |
22 |
23 |
24 |
25 | org.projectlombok:*
26 |
27 |
28 |
29 |
30 | me.x150.j2cc.Main
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | maven-dependency-plugin
39 | 3.6.1
40 |
41 |
42 | maven-compiler-plugin
43 |
44 | 21
45 | 21
46 | false
47 |
48 |
49 |
50 |
51 |
52 |
53 | UTF-8
54 | 20
55 | 20
56 |
57 |
58 |
--------------------------------------------------------------------------------
/testjar/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | me.x150.j2cc
8 | j2cc-bom
9 | 1.0-SNAPSHOT
10 |
11 |
12 | ${j2cc.version}
13 | testjar
14 |
15 |
16 | 20
17 | 20
18 | UTF-8
19 |
20 |
21 |
22 | org.ow2.asm
23 | asm
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | me.x150.j2cc
36 | annotations
37 | ${j2cc.version}
38 | compile
39 |
40 |
41 |
42 |
43 |
44 | org.apache.maven.plugins
45 | maven-shade-plugin
46 | 3.4.1
47 |
48 |
49 |
50 | package
51 |
52 | shade
53 |
54 |
55 |
56 |
57 | org.projectlombok:*
58 |
59 |
60 |
61 |
62 |
64 | me.x150.j2cc.Main
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | org.apache.maven.plugins
91 | maven-dependency-plugin
92 | 3.6.1
93 |
94 |
95 | org.apache.maven.plugins
96 | maven-compiler-plugin
97 |
98 | 21
99 | 21
100 | false
101 |
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/MethodSimTest.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc;
2 |
3 | public class MethodSimTest {
4 | private static int doOp(int a, int b) {
5 | a ^= 123;
6 | a = a | b;
7 | if (a != 0) a = doOp(a, b);
8 | return a * b;
9 | }
10 | public static void main(String[] args) {
11 | int someLocal = doOp(10, 20);
12 | System.out.println(someLocal);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/SimTest.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc;
2 |
3 | public class SimTest {
4 | public static void main(String[] args) {
5 | String what = new String("guh");
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/a2.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc;
2 |
3 | //@Nativeify
4 | public class a2 {
5 |
6 | public static void main(String[] args) {
7 | System.out.println(0);
8 | }
9 | }
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/clSplit/A.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.clSplit;
2 |
3 | public abstract class A {
4 | protected String doStuff() {return "A";}
5 | }
6 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/clSplit/B.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.clSplit;
2 |
3 | public class B extends A {
4 | @Override
5 | protected String doStuff() {
6 | return "B";
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/clSplit/C.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.clSplit;
2 |
3 | public class C extends B {
4 | @Override
5 | protected String doStuff() {
6 | return "C";
7 | }
8 |
9 | public static void main(String[] args) {
10 | B bruh = new C();
11 | System.out.println(bruh.doStuff());
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/inheri/Child.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.inheri;
2 |
3 | public class Child extends Parent {
4 | @Override
5 | public String get() {
6 | return "child, " + super.get();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/inheri/Parent.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.inheri;
2 |
3 | //@Nativeify
4 | public class Parent {
5 | public static void main(String[] args) {
6 | System.out.println("real");
7 | }
8 |
9 | public String get() {
10 | return "parent";
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/testjar/src/main/java/me/x150/j2cc/inheri/SecondChild.java:
--------------------------------------------------------------------------------
1 | package me.x150.j2cc.inheri;
2 |
3 | public class SecondChild extends Child {
4 | @Override
5 | public String get() {
6 | return "child2nd, " + super.get();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/testjar/src/main/resources/a.txt:
--------------------------------------------------------------------------------
1 | hi
--------------------------------------------------------------------------------
/util/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.26)
2 | project(util)
3 |
4 | set(ENV{JAVA_HOME} "/home/x150/.jdks/openjdk-22.0.1")
5 |
6 | find_package (Java REQUIRED)
7 | find_package (JNI REQUIRED)
8 | include (UseJava)
9 |
10 | set(CMAKE_CXX_STANDARD 23)
11 |
12 | add_executable(util
13 | util.h
14 | util.cpp
15 | export.h
16 | antiHook.cpp
17 | antiHook.h
18 | chacha20.h
19 | chacha20.cpp
20 | test_main.cpp
21 | )
22 |
23 | #target_include_directories (util PRIVATE "/home/x150/.jdks/openjdk-20.0.2/include" "/home/x150/.jdks/openjdk-20.0.2/include/linux")
24 | target_include_directories (util PRIVATE ${JNI_INCLUDE_DIRS})
25 | target_link_libraries (util PRIVATE ${JNI_LIBRARIES})
--------------------------------------------------------------------------------
/util/antiHook.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by x150 on 26 Sep 2024.
3 | //
4 |
5 | #pragma once
6 |
7 | #include
8 |
9 | void checkForHookedFunctions(JNIEnv*);
--------------------------------------------------------------------------------
/util/build_comptime_library.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/zsh
2 |
3 | cdir=${0:a:h}
4 |
5 | if [[ $# == 0 ]]; then
6 | targets=("x86_64-linux-gnu" "libnativeUtils.so" "x86_64-windows-gnu" "nativeUtils.dll" "x86_64-macos-none" "libnativeUtils.dylib")
7 | else
8 | targets=("$@")
9 | fi
10 | for ((i = 1; i <= $#targets; i+=2)); do
11 | nt="${targets[i]}"
12 | name="${targets[i + 1]}"
13 | echo "building $nt into $name"
14 | "$cdir"/../zig-compiler/zig c++ -Xpreprocessor -DCOMPILING_COMPTIME_LIB -shared -O2 -fno-exceptions -o "$cdir/../natives/$name" -I "$cdir" -std=c++23 "-Wl,-S" \
15 | chacha20.cpp \
16 | -target "$nt"
17 | done
18 |
19 | rm -v -- "$cdir"/../natives/*.lib
--------------------------------------------------------------------------------
/util/chacha20.cpp:
--------------------------------------------------------------------------------
1 | #include "chacha20.h"
2 |
3 | #include
4 |
5 | #define ROTL(a,b) (((a) << (b)) | ((a) >> (32 - (b))))
6 | #define QR(a, b, c, d) ( \
7 | a += b, d ^= a, d = ROTL(d, 16), \
8 | c += d, b ^= c, b = ROTL(b, 12), \
9 | a += b, d ^= a, d = ROTL(d, 8), \
10 | c += d, b ^= c, b = ROTL(b, 7))
11 | CCEXPORT void chacha_block(uint32_t out[16], uint32_t const in[16], int rounds)
12 | {
13 | int i;
14 | uint32_t x[16];
15 |
16 | for (i = 0; i < 16; ++i)
17 | x[i] = in[i];
18 | // 10 loops × 2 rounds/loop = 20 rounds
19 | for (i = 0; i < rounds; i += 2) {
20 |
21 | // Odd round
22 | QR(x[0], x[4], x[ 8], x[12]); // column 1
23 | QR(x[1], x[5], x[ 9], x[13]); // column 2
24 | QR(x[2], x[6], x[10], x[14]); // column 3
25 | QR(x[3], x[7], x[11], x[15]); // column 4
26 | // Even round
27 | QR(x[0], x[5], x[10], x[15]); // diagonal 1 (main diagonal)
28 | QR(x[1], x[6], x[11], x[12]); // diagonal 2
29 | QR(x[2], x[7], x[ 8], x[13]); // diagonal 3
30 | QR(x[3], x[4], x[ 9], x[14]); // diagonal 4
31 | }
32 | for (i = 0; i < 16; ++i)
33 | out[i] = x[i] + in[i];
34 | }
35 |
36 | CCEXPORT void chacha_block_8(uint8_t out[16 * 4], uint8_t const in[16 * 4], int rounds) {
37 | chacha_block(reinterpret_cast(out), reinterpret_cast(in), rounds);
38 | }
39 |
40 | inline uint32_t hash(uint32_t x) {
41 | x = ((x >> 16) ^ x) * 0x45d9f3b;
42 | x = ((x >> 16) ^ x) * 0x45d9f3b;
43 | x = (x >> 16) ^ x;
44 | return x;
45 | }
46 |
47 | CCEXPORT void fuckMyShitUp(const unsigned char *keyData, unsigned char chachaTable[], size_t nBlocks) {
48 | // for (size_t o = 0; o < 48; o++) {
49 | // printf("%d ", keyData[o]);
50 | // }
51 | // puts("");
52 | for (size_t block_index = 0; block_index < nBlocks; block_index++) {
53 | unsigned char block[64];
54 | unsigned char* blockStart = chachaTable + (block_index * 64);
55 | memcpy(block, keyData, 48); // copy key data
56 |
57 | uint32_t nonce = hash(block_index);
58 |
59 | uint32_t* blockAsUint = reinterpret_cast(block+48);
60 | blockAsUint[0] = block_index; // 48 + 0 * 4 = 48
61 | blockAsUint[1] = block_index; // 48 + 1 * 4 = 52
62 | blockAsUint[2] = nonce; // 48 + 2 * 4 = 56
63 | blockAsUint[3] = nonce; // 48 + 3 * 4 = 60
64 |
65 | chacha_block_8(blockStart, block, 20);
66 | }
67 | }
--------------------------------------------------------------------------------
/util/chacha20.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "export.h"
3 |
4 | #include
5 | #include // for some ungodly reason this is needed to get size_t on x8664 linux
6 |
7 | #ifdef COMPILING_COMPTIME_LIB
8 | #define CCEXPORT EXPORT
9 | #else
10 | #define CCEXPORT
11 | #endif
12 |
13 | CCEXPORT void chacha_block(uint32_t out[16], uint32_t const in[16], int rounds);
14 | CCEXPORT void chacha_block_8(uint8_t out[16 * 4], uint8_t const in[16 * 4], int rounds);
15 |
16 | /**
17 | *
18 | * @param keyData 48 chars
19 | * @param chachaTable nBlocks * 64 chars
20 | * @param nBlocks how many blocks to generate
21 | */
22 | CCEXPORT void fuckMyShitUp(const unsigned char *keyData, unsigned char chachaTable[], size_t nBlocks);
--------------------------------------------------------------------------------
/util/export.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #ifdef _WIN32
4 | #define EXPORT extern "C" __declspec(dllexport)
5 | #else
6 | #define EXPORT extern "C"
7 | #endif
8 |
--------------------------------------------------------------------------------
/util/test_main.cpp:
--------------------------------------------------------------------------------
1 | #include "util.h"
2 |
3 | static char constants[] = "\x84\x25\x8C\x08\x4D\x89\xC1\x3E\x34\x3C\x66\x9A\x9C\x8A\xFA\x4E\x76\x46\x58\x9E\x58\x1B\xB9\xB7\xF4\x67\xFC\x69\xDE\xA6\xD9\x4E\xF2\x34\x38\x23\xA7\x3D\x25\xBF\x8F\x98\xC5\xAE\xE6\x66\x43\x43\x34\x4B\xCC\x5F\x0B\x0E\x9C\x5A\x17\x64\xA9\x3F\xA3\xE6\x55";
4 |
5 | static StringCpInfo scpF {
6 | .ptr = constants,
7 | .n = sizeof(constants) - 1
8 | };
9 |
10 | StringCpInfo* stringConstantPool = &scpF;
11 |
12 | int main() {
13 | // printf("%zu\n", sizeof(theConsts));
14 | unsigned char key[48] = {};
15 | const char* ok = "\x67\x6F\x6F\x64\x6D\x6F\x72\x6E\x69\x6E\x67\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
16 | mempcpy(key, ok, 48);
17 | // unsigned char chacha[64] = {};
18 | // fuckMyShitUp(key, chacha, 1);
19 | // for (unsigned char chacha1 : chacha) {
20 | // printf("%u\n", chacha1);
21 | // }
22 | // puts("");
23 | initChachaTable(key);
24 | for (size_t i = 0; i < scpF.n; i++) {
25 | printf("%x ", static_cast(scpF.ptr[i]));
26 | }
27 | puts("");
28 | //
29 | // for (size_t i = 0; i < scpF.n; i++) {
30 | // printf("%x ", static_cast(scpF.ptr[i]));
31 | // }
32 | // puts("");
33 |
34 | // uint32_t* blockAsUint2 = reinterpret_cast(chachaTable);
35 | // for (size_t base = 0; base < (1024/4); base+=4) {
36 | // printf("%08x %08x %08x %08x\n", blockAsUint2[base], blockAsUint2[base+1], blockAsUint2[base+2], blockAsUint2[base+3]);
37 | // }
38 | }
--------------------------------------------------------------------------------
/util/util.h:
--------------------------------------------------------------------------------
1 | //
2 | // Created by x150 on 24.08.23.
3 | //
4 |
5 | #pragma once
6 |
7 | #include
8 |
9 | #include "export.h"
10 |
11 | #define assertm(exp, msg) assert(((void)msg, exp))
12 |
13 | //#define J2CC_DBG
14 |
15 | #ifdef J2CC_DBG
16 | #define DBG(msgf, ...) printf("[j2cc] %s at %d: " msgf "\n", __FILE_NAME__, __LINE__ __VA_OPT__(,) __VA_ARGS__); fflush(stdout);
17 | #else
18 | #define DBG(msg_expr, ...)
19 | #endif
20 |
21 | // these are defined by compiler in actual build - dummy definition
22 | #ifndef DYN_CACHE_SIZE
23 | #define DYN_CACHE_SIZE 0
24 | #endif
25 |
26 | #ifndef CLAZZ_CACHE_SIZE
27 | #define CLAZZ_CACHE_SIZE 0
28 | #endif
29 | #ifndef FIELD_CACHE_SIZE
30 | #define FIELD_CACHE_SIZE 0
31 | #endif
32 | #ifndef METHOD_CACHE_SIZE
33 | #define METHOD_CACHE_SIZE 0
34 | #endif
35 |
36 | // /compiler defined dummy definitions
37 |
38 | #include
39 | #include
40 | #include
41 | #include
42 |
43 | #include "chacha20.h"
44 |
45 | struct StringCpInfo {
46 | char* ptr;
47 | size_t n;
48 | };
49 |
50 | extern StringCpInfo* stringConstantPool;
51 |
52 | #define STRING_CP(i) (stringConstantPool->ptr+(i))
53 |
54 | inline size_t roundUp(size_t n, size_t x) {
55 | return ((n + x - 1) / x) * x;
56 | }
57 |
58 | static void initChachaTable(JNIEnv* env, jbyteArray jba) {
59 | if (stringConstantPool == nullptr) return;
60 | unsigned char keyData[48];
61 | env->GetByteArrayRegion(jba, 0, 48, reinterpret_cast(keyData));
62 | size_t ctLen = roundUp(stringConstantPool->n, 64);
63 | unsigned char chachaTable[ctLen];
64 | // one block is 64 bytes. we have n bytes in total
65 | size_t nBlocks = ctLen / 64;
66 | fuckMyShitUp(keyData, chachaTable, nBlocks);
67 | for (size_t i = 0; i < stringConstantPool->n; i++) {
68 | stringConstantPool->ptr[i] = static_cast(stringConstantPool->ptr[i] ^ chachaTable[i]);
69 | }
70 | }
71 |
72 | static bool stringsEqual(JNIEnv* env, jstring a, jstring b) {
73 | jsize lenA = env->GetStringLength(a);
74 | if (jsize lenB = env->GetStringLength(b); lenA != lenB) return false;
75 | const jchar* cA = env->GetStringCritical(a, nullptr);
76 | const jchar* cB = env->GetStringCritical(b, nullptr);
77 | bool res = true;
78 | for(jsize p = 0; p < lenA; ++p) {
79 | if (cA[p] != cB[p]) { res = false; break; }
80 | }
81 | env->ReleaseStringCritical(a, cA);
82 | env->ReleaseStringCritical(b, cB);
83 | return res;
84 | }
85 |
86 | namespace cache {
87 | static jclass clazzCache[CLAZZ_CACHE_SIZE] = {};
88 |
89 | static jmethodID methodCache[METHOD_CACHE_SIZE] = {};
90 | static jfieldID fieldCache[FIELD_CACHE_SIZE] = {};
91 |
92 | static jobject dynamicCache[DYN_CACHE_SIZE] = {};
93 |
94 | static std::recursive_mutex clazzMutex;
95 | static std::mutex methodMutex;
96 | static std::mutex fieldMutex;
97 |
98 | static jclass java_lang_Class = nullptr;
99 | static jmethodID java_lang_Class_getName = nullptr;
100 |
101 | void ensureClazz(JNIEnv* env);
102 |
103 | std::string clazzName(JNIEnv* env, jclass clazz, bool* ok);
104 |
105 | bool getCachedValue(int key, jobject* target);
106 | void putCachedValue(int key, jobject target);
107 |
108 | jclass findClass(JNIEnv *env, const char* name, int slot);
109 | jfieldID getNonstaticField(JNIEnv* env, const jclass& clazz, const char* name, const char* desc, int index);
110 | jfieldID getStaticField(JNIEnv* env, const jclass& clazz, const char* name, const char* desc, int index);
111 | jmethodID getNonstaticMethod(JNIEnv* env, const jclass& clazz, const char* name, const char* desc, int index);
112 | jmethodID getStaticMethod(JNIEnv* env, const jclass& clazz, const char* name, const char* desc, int index);
113 | }
114 |
115 | static struct {
116 | bool initialized = false;
117 | jclass java_lang_NullpointerException = nullptr;
118 | jclass java_lang_Object = nullptr;
119 | jclass IntegerCache = nullptr;
120 | jclass java_lang_Integer = nullptr;
121 | jclass java_lang_Character = nullptr;
122 | jclass java_lang_CharacterCache = nullptr;
123 | } commonClasses;
124 |
125 | void ensureCommonClassesInit(JNIEnv* env);
126 |
127 | #define J2_INTRINSIC(cl, name, ...) j2cc_intrinsic_ ## cl ## _ ## name(JNIEnv* env, jobject instance __VA_OPT__(,) __VA_ARGS__)
128 | #define J2_INTRINSIC_STATIC(cl, name, ...) j2cc_intrinsic_ ## cl ## _ ## name(JNIEnv* env __VA_OPT__(,) __VA_ARGS__)
129 | #define J2_INTRINSIC_HEADER(methodRep, ...) if (!instance) {\
130 | ensureCommonClassesInit(env); \
131 | env->ThrowNew(commonClasses.java_lang_NullpointerException, "Cannot invoke " methodRep);\
132 | }
133 |
134 | jclass J2_INTRINSIC(java_lang_Object, getClass);
135 |
136 | jint J2_INTRINSIC(java_lang_String, length);
137 | jboolean J2_INTRINSIC(java_lang_String, isEmpty);
138 | jboolean J2_INTRINSIC(java_lang_String, equals, jobject thatStr);
139 |
140 | jobject J2_INTRINSIC_STATIC(java_lang_Integer, valueOf, jint value);
141 |
142 | jobject J2_INTRINSIC_STATIC(java_lang_Character, valueOf, jchar value);
--------------------------------------------------------------------------------