diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1884b2a..054060a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,14 +8,17 @@ jobs: os: - os: ubuntu-latest compiler_install_command: sudo apt install g++ cmake + krakatau_install_command: sudo cp ./target/release/krak2 /usr/local/bin/ cc: gcc cxx: g++ - os: macos-latest compiler_install_command: brew install cmake + krakatau_install_command: cp ./target/release/krak2 /usr/local/bin/ cc: clang cxx: clang++ - os: windows-latest compiler_install_command: choco install cmake + krakatau_install_command: copy ./target/release/krak2.exe C:/Windows prebuild_script: '"C:/Program Files (x86)/Microsoft Visual Studio/2019/Enterprise/VC/Auxiliary/Build/vcvars64.bat"' cc: cl cxx: cl @@ -28,6 +31,15 @@ jobs: with: java-version: ${{ matrix.java }} distribution: temurin + - name: Install Rust toolchain for Krakatau + uses: dtolnay/rust-toolchain@stable + - name: Build Krakatau + run: | + git clone https://github.com/Storyyeller/Krakatau.git + cd Krakatau + cargo build --release + - name: Install Krakatau + run: ${{ matrix.os.krakatau_install_command }} - name: Install C++ dependencies run: ${{ matrix.os.compiler_install_command }} - name: Build with Gradle diff --git a/obfuscator/src/test/java/by/radioegor146/ClassicTest.java b/obfuscator/src/test/java/by/radioegor146/ClassicTest.java index 93f6aac..e4c8896 100644 --- a/obfuscator/src/test/java/by/radioegor146/ClassicTest.java +++ b/obfuscator/src/test/java/by/radioegor146/ClassicTest.java @@ -11,8 +11,6 @@ import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; public class ClassicTest implements Executable { @@ -20,10 +18,12 @@ public class ClassicTest implements Executable { private final Path testData; private Path temp; private final String testName; + private final boolean useKrakatau; - ClassicTest(Path path, String testName) { + ClassicTest(Path path, String testName, boolean useKrakatau) { testData = path; this.testName = testName; + this.useKrakatau = useKrakatau; } private void clean() { @@ -50,18 +50,24 @@ public void execute() throws Throwable { temp = Files.createTempDirectory(String.format("native-obfuscator-test-%s-", testData.toFile().getName())); Path tempSource = temp.resolve("source"); + Path tempKrakatauSource = temp.resolve("source-krakatau"); Path tempClasses = temp.resolve("classes"); Files.createDirectories(tempSource); + Files.createDirectories(tempKrakatauSource); Files.createDirectories(tempClasses); Path idealJar = temp.resolve("test.jar"); List javaFiles = new ArrayList<>(); + List krakatauFiles = new ArrayList<>(); List resourceFiles = new ArrayList<>(); Files.find(testData, 10, (path, attr) -> true) .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java")) .forEach(javaFiles::add); - Files.find(testData, 10, (path, attr) -> attr.isDirectory() || !path.toString().endsWith(".java")) + Files.find(testData, 10, (path, attr) -> true) + .filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".j")) + .forEach(krakatauFiles::add); + Files.find(testData, 10, (path, attr) -> attr.isDirectory() || (!path.toString().endsWith(".java") && !path.toString().endsWith(".j"))) .filter(Files::isRegularFile) .forEach(resourceFiles::add); @@ -74,11 +80,12 @@ public void execute() throws Throwable { if (!mainClassOptional.isPresent()) { System.out.println("Can't find main class"); - return; + throw new RuntimeException("Main class not found"); } - javaFiles.forEach(unchecked(p -> Files.copy(p, tempSource.resolve(p.getFileName())))); - resourceFiles.forEach(unchecked(p -> { + javaFiles.forEach(uncheckedConsumer(p -> Files.copy(p, tempSource.resolve(p.getFileName())))); + krakatauFiles.forEach(uncheckedConsumer(p -> Files.copy(p, tempKrakatauSource.resolve(p.getFileName())))); + resourceFiles.forEach(uncheckedConsumer(p -> { Path target = temp.resolve(testData.relativize(p)); Files.createDirectories(target.getParent()); Files.copy(p, target); @@ -92,6 +99,17 @@ public void execute() throws Throwable { ProcessHelper.run(temp, 10_000, javacParameters) .check("Compilation"); + if (useKrakatau) { + System.out.println("Compiling Krakatau..."); + krakatauFiles.stream().forEach(uncheckedConsumer(path -> { + List krakatauParameters = new ArrayList<>(Arrays.asList("krak2", "asm", "--out", + tempClasses.resolve(testData.relativize(path)).toString().replaceAll("\\.j$", ".class"), + path.toString())); + ProcessHelper.run(temp, 1_000, krakatauParameters) + .check("Krakatau compilation"); + })); + } + List jarParameters = new ArrayList<>(Arrays.asList( "jar", "cvfe", idealJar.toString(), mainClassOptional.get(), "-C", tempClasses + File.separator, ".")); @@ -141,7 +159,7 @@ public void execute() throws Throwable { Files.find(tempCpp.resolve("build").resolve("lib"), 1, (path, args) -> Files.isRegularFile(path)) - .forEach(unchecked(p -> Files.copy(p, tempOutput.resolve(p.getFileName())))); + .forEach(uncheckedConsumer(p -> Files.copy(p, tempOutput.resolve(p.getFileName())))); System.out.println("Running test..."); @@ -157,21 +175,13 @@ public void execute() throws Throwable { testRunResult.check("Test run"); if (!testRunResult.stdout.equals(idealRunResult.stdout)) { - // Some tests are random based - Pattern testResult = Pattern.compile("^Passed = \\d+,? failed = (\\d+)$", Pattern.MULTILINE); - Matcher matcher = testResult.matcher(testRunResult.stdout); - if (matcher.find()) { - if (!matcher.group(1).equals("0")) { - fail(testRunResult, idealRunResult); - } - } else { - fail(testRunResult, idealRunResult); - } + fail(testRunResult, idealRunResult); } System.out.println("OK"); } - // clean(); + + clean(); } catch (IOException | RuntimeException e) { e.printStackTrace(System.err); throw e; @@ -186,17 +196,17 @@ private void fail(ProcessResult testRun, ProcessResult ideaRun) { throw new RuntimeException("Ideal != Test"); } - private Predicate uncheckedPredicate(UncheckedPredicate consumer) { + private Predicate uncheckedPredicate(UncheckedPredicate predicate) { return value -> { try { - return consumer.test(value); + return predicate.test(value); } catch (Exception e) { throw new RuntimeException(e); } }; } - private Consumer unchecked(UncheckedConsumer consumer) { + private Consumer uncheckedConsumer(UncheckedConsumer consumer) { return value -> { try { consumer.accept(value); diff --git a/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java b/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java deleted file mode 100644 index d14a45c..0000000 --- a/obfuscator/src/test/java/by/radioegor146/StringConcatFactoryTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package by.radioegor146; - -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Handle; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.*; - -import java.io.FileOutputStream; -import java.io.IOException; - -public class StringConcatFactoryTest implements Opcodes { - public static void main(String[] args) throws IOException { - ClassNode classNode = new ClassNode(); - classNode.version = V17; - classNode.access = ACC_PUBLIC | ACC_SUPER; - classNode.superName = "java/lang/Object"; - classNode.name = "TestStringConcatFactory"; - MethodNode method = new MethodNode(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null); - - - method.instructions.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); - - method.instructions.add(new InsnNode(ICONST_1)); - method.instructions.add(new InsnNode(ICONST_2)); - method.instructions.add(new InsnNode(ICONST_3)); - - Handle handle = new Handle(H_INVOKESTATIC, - "java/lang/invoke/StringConcatFactory", - "makeConcatWithConstants", - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", - false); - method.instructions.add(new InvokeDynamicInsnNode("makeConcatWithConstants", - "(III)Ljava/lang/String;", - handle, - "\u0001-\u0001-\u0001-\u0002-\u0002-\u0002", - 3.14, 123, true)); - method.instructions.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false)); - method.instructions.add(new InsnNode(RETURN)); - // System.out.println(String); - classNode.methods.add(method); - - // read - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); - classNode.accept(writer); - - // save - FileOutputStream stream = new FileOutputStream("TestStringConcatFactory.class"); - stream.write(writer.toByteArray()); - stream.close(); - } -} diff --git a/obfuscator/src/test/java/by/radioegor146/TestsGenerator.java b/obfuscator/src/test/java/by/radioegor146/TestsGenerator.java index 3c8311d..d329787 100644 --- a/obfuscator/src/test/java/by/radioegor146/TestsGenerator.java +++ b/obfuscator/src/test/java/by/radioegor146/TestsGenerator.java @@ -43,11 +43,27 @@ public Stream generateTests() throws URISyntaxException, IOExceptio URL tests = TestsGenerator.class.getClassLoader().getResource("tests"); Objects.requireNonNull(tests, "No tests dir in resources"); + boolean useKrakatau; + + try { + Process process = new ProcessBuilder().command("krak2", "-V").start(); + process.waitFor(); + if (process.exitValue() != 0) { + throw new RuntimeException("krak2 -V failed"); + } + useKrakatau = true; + } catch (Exception e) { + System.err.println("No Krakatau2 found (krak2), so tests with it will fail"); + useKrakatau = false; + } + + boolean finalUseKrakatau = useKrakatau; + Path testDir = Paths.get(tests.toURI()); return Files.walk(testDir, FileVisitOption.FOLLOW_LINKS).filter(Files::isDirectory) .filter(TestsGenerator::hasJavaFiles).filter(TestsGenerator::testAllowed) .map(p -> DynamicTest.dynamicTest(testDir.relativize(p).toString(), - new ClassicTest(p, testDir.relativize(p).toString()))); + new ClassicTest(p, testDir.relativize(p).toString(), finalUseKrakatau))); } private static boolean hasJavaFiles(Path path) { diff --git a/obfuscator/test_data/tests/pull-requests/PullRequest72/Main.java b/obfuscator/test_data/tests/pull-requests/PullRequest72/Main.java new file mode 100644 index 0000000..fcf59bc --- /dev/null +++ b/obfuscator/test_data/tests/pull-requests/PullRequest72/Main.java @@ -0,0 +1,11 @@ +public class Main { + public static void main(String[] args) throws Exception { + try { + Class.forName("java.lang.invoke.StringConcatFactory"); + } catch (Exception e) { + System.out.println("No java/lang/invoke/StringConcatFactory found, probably Java 1.8 or older"); + return; + } + Class.forName("TestStringConcatFactory").getMethod("test").invoke(null); + } +} \ No newline at end of file diff --git a/obfuscator/test_data/tests/pull-requests/PullRequest72/TestStringConcatFactory.j b/obfuscator/test_data/tests/pull-requests/PullRequest72/TestStringConcatFactory.j new file mode 100644 index 0000000..ef57bd9 --- /dev/null +++ b/obfuscator/test_data/tests/pull-requests/PullRequest72/TestStringConcatFactory.j @@ -0,0 +1,19 @@ +.version 55 0 +.class public super TestStringConcatFactory +.super java/lang/Object + +.method public static test : ()V + .code stack 4 locals 1 +L0: getstatic Field java/lang/System out Ljava/io/PrintStream; +L3: iconst_1 +L4: iconst_2 +L5: iconst_3 +L6: invokedynamic [_28] +L11: invokevirtual Method java/io/PrintStream println (Ljava/lang/String;)V +L14: return +L15: + .end code +.end method +.bootstrapmethods +.const [_28] = InvokeDynamic invokeStatic Method java/lang/invoke/StringConcatFactory makeConcatWithConstants (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite; String "\u0001-\u0001-\u0001-\u0002-\u0002-\u0002" Double 3.14e0 Int 123 Int 1 : makeConcatWithConstants (III)Ljava/lang/String; +.end class