├── .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 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 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 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 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> 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); --------------------------------------------------------------------------------