Java 反编译工具的使用与对比分析
# 前言
Java 反编译,一听可能觉得高深莫测,其实反编译并不是什么特别高级的操作,Java 对于 Class 字节码文件的生成有着严格的要求,如果你非常熟悉 Java 虚拟机规范,了解 Class 字节码文件中一些字节的作用,那么理解反编译的原理并不是什么问题。 甚至像下面这样的 Class 文件你都能看懂一二。
一般在逆向研究和代码分析中,反编译用到的比较多。不过在日常开发中,有时候只是简单的看一下所用依赖类的反编译,也是十分重要的。
恰好最近工作中也需要用到 Java 反编译,所以这篇文章介绍目前常见的的几种 Java 反编译工具的使用,在文章的最后也会通过编译速度、语法支持以及代码可读性三个维度,对它们进行测试,分析几款工具的优缺点。
# Procyon
Github 链接:https://github.com/mstrobel/procyon (opens new window) Procyon 不仅仅是反编译工具,它其实是专注于 Java 代码的生成和分析的一整套的 Java 元编程工具。 主要包括下面几个部分:
- Core Framework
- Reflection Framework
- Expressions Framework
- Compiler Toolset (Experimental)
- Java Decompiler (Experimental)
可以看到反编译只是 Procyon 的其中一个模块,Procyon 原来托管于 bitbucket,后来迁移到了 GitHub,根据 GitHub 的提交记录来看,也有将近两年没有更新了。不过也有依赖 Procyon 的其他的开源反编译工具如** decompiler-procyon**,更新频率还是很高的,下面也会选择这个工具进行反编译测试。
# 使用 Procyon
<!-- https://mvnrepository.com/artifact/org.jboss.windup.decompiler/decompiler-procyon -->
<dependency>
<groupId>org.jboss.windup.decompiler</groupId>
<artifactId>decompiler-procyon</artifactId>
<version>5.1.4.Final</version>
</dependency>
2
3
4
5
6
写一个简单的反编译测试。
package com.wdbyte.decompiler;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.List;
import org.jboss.windup.decompiler.api.DecompilationFailure;
import org.jboss.windup.decompiler.api.DecompilationListener;
import org.jboss.windup.decompiler.api.DecompilationResult;
import org.jboss.windup.decompiler.api.Decompiler;
import org.jboss.windup.decompiler.procyon.ProcyonDecompiler;
/**
* Procyon 反编译测试
*
* @author https://github.com/niumoo
* @date 2021/05/15
*/
public class ProcyonTest {
public static void main(String[] args) throws IOException {
Long time = procyon("decompiler.jar", "procyon_output_jar");
System.out.println(String.format("decompiler time: %dms", time));
}
public static Long procyon(String source,String targetPath) throws IOException {
long start = System.currentTimeMillis();
Path outDir = Paths.get(targetPath);
Path archive = Paths.get(source);
Decompiler dec = new ProcyonDecompiler();
DecompilationResult res = dec.decompileArchive(archive, outDir, new DecompilationListener() {
public void decompilationProcessComplete() {
System.out.println("decompilationProcessComplete");
}
public void decompilationFailed(List<String> inputPath, String message) {
System.out.println("decompilationFailed");
}
public void fileDecompiled(List<String> inputPath, String outputPath) {
}
public boolean isCancelled() {
return false;
}
});
if (!res.getFailures().isEmpty()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed decompilation of " + res.getFailures().size() + " classes: ");
Iterator failureIterator = res.getFailures().iterator();
while (failureIterator.hasNext()) {
DecompilationFailure dex = (DecompilationFailure)failureIterator.next();
sb.append(System.lineSeparator() + " ").append(dex.getMessage());
}
System.out.println(sb.toString());
}
System.out.println("Compilation results: " + res.getDecompiledFiles().size() + " succeeded, " + res.getFailures().size() + " failed.");
dec.close();
Long end = System.currentTimeMillis();
return end - start;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Procyon 在反编译时会实时输出反编译文件数量的进度情况,最后还会统计反编译成功和失败的 Class 文件数量。
....
五月 15, 2021 10:58:28 下午 org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3 call
信息: Decompiling 650 / 783
五月 15, 2021 10:58:30 下午 org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3 call
信息: Decompiling 700 / 783
五月 15, 2021 10:58:37 下午 org.jboss.windup.decompiler.procyon.ProcyonDecompiler$3 call
信息: Decompiling 750 / 783
decompilationProcessComplete
Compilation results: 783 succeeded, 0 failed.
decompiler time: 40599ms
2
3
4
5
6
7
8
9
10
# Procyon GUI
对于 Procyon 反编译来说,在 GitHub 上也有基于此实现的开源 GUI 界面,感兴趣的可以下载尝试。 Github 地址:https://github.com/deathmarine/Luyten (opens new window)
# CFR
GitHub 地址:https://github.com/leibnitz27/cfr (opens new window) CFR 官方网站:http://www.benf.org/other/cfr/ (opens new window)(访问可能需要FQ) Maven 仓库: https://mvnrepository.com/artifact/org.benf/cfr (opens new window)
CFR(Class File Reader) 可以支持 Java 9、Java 12、Java 14 以及其他的最新版 Java 代码的反编译工作。而且 CFR 本身的代码是由 Java 6 编写,所以基本可以使用 CFR 在任何版本的 Java 程序中。值得一提的是,使用 CFR 甚至可以将使用其他语言编写的的 JVM 类文件反编译回 Java 文件。
# CFR 命令行使用
使用 CFR 反编译时,你可以下载已经发布的 JAR 包,进行命令行反编译,也可以使用 Maven 引入的方式,在代码中使用。下面先说命令行运行的方式。
直接在 GitHub Tags 下载 (opens new window)已发布的最新版 JAR. 可以直接运行查看帮助。
# 查看帮助
java -jar cfr-0.151.jar --help
2
如果只是反编译某个 class.
# 反编译 class 文件,结果输出到控制台
java -jar cfr-0.151.jar WindupClasspathTypeLoader.class
# 反编译 class 文件,结果输出到 out 文件夹
java -jar cfr-0.151.jar WindupClasspathTypeLoader.class --outputpath ./out
2
3
4
反编译某个 JAR.
# 反编译 jar 文件,结果输出到 output_jar 文件夹
➜ Desktop java -jar cfr-0.151.jar decompiler.jar --outputdir ./output_jar
Processing decompiler.jar (use silent to silence)
Processing com.strobel.assembler.metadata.ArrayTypeLoader
Processing com.strobel.assembler.metadata.ParameterDefinition
Processing com.strobel.assembler.metadata.MethodHandle
Processing com.strobel.assembler.metadata.signatures.FloatSignature
.....
2
3
4
5
6
7
8
反编译结果会按照 class 的包路径写入到指定文件夹中。
# CFR 代码中使用
添加依赖这里不提。
<!-- https://mvnrepository.com/artifact/org.benf/cfr -->
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<version>0.151</version>
</dependency>
2
3
4
5
6
实际上我在官方网站和 GitHub 上都没有看到具体的单元测试示例。不过没有关系,既然能在命令行运行,那么直接在 IDEA 中查看反编译后的 Main 方法入口,看下命令行是怎么执行的,就可以写出自己的单元测试了。
package com.wdbyte.decompiler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.benf.cfr.reader.api.CfrDriver;
import org.benf.cfr.reader.util.getopt.OptionsImpl;
/**
* CFR Test
*
* @author https://github.com/niumoo
* @date 2021/05/15
*/
public class CFRTest {
public static void main(String[] args) throws IOException {
Long time = cfr("decompiler.jar", "./cfr_output_jar");
System.out.println(String.format("decompiler time: %dms", time));
// decompiler time: 11655ms
}
public static Long cfr(String source, String targetPath) throws IOException {
Long start = System.currentTimeMillis();
// source jar
List<String> files = new ArrayList<>();
files.add(source);
// target dir
HashMap<String, String> outputMap = new HashMap<>();
outputMap.put("outputdir", targetPath);
OptionsImpl options = new OptionsImpl(outputMap);
CfrDriver cfrDriver = new CfrDriver.Builder().withBuiltOptions(options).build();
cfrDriver.analyse(files);
Long end = System.currentTimeMillis();
return (end - start);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# JD-Core
GiHub 地址:https://github.com/java-decompiler/jd-core (opens new window) JD-core 官方网址:https://java-decompiler.github.io/ (opens new window) JD-core 是一个的独立的 Java 库,可以用于 Java 的反编译,支持从 Java 1 至 Java 12 的字节码反编译,包括 Lambda 表达式、方式引用、默认方法等。知名的 JD-GUI 和 Eclipse 无缝集成反编译引擎就是 JD-core。 JD-core 提供了一些反编译的核心功能,也提供了单独的 Class 反编译方法,但是如果你想在自己的代码中去直接反编译整个 JAR 包,还是需要一些改造的,如果是代码中有匿名函数,Lambda 等,虽然可以直接反编译,不过也需要额外考虑。
# 使用 JD-core
<!-- https://mvnrepository.com/artifact/org.jd/jd-core -->
<dependency>
<groupId>org.jd</groupId>
<artifactId>jd-core</artifactId>
<version>1.1.3</version>
</dependency>
2
3
4
5
6
为了可以反编译整个 JAR 包,使用的代码我做了一些简单改造,以便于最后一部分的对比测试,但是这个示例中没有考虑内部类,Lambda 等会编译出多个 Class 文件的情况,所以不能直接使用在生产中。
package com.wdbyte.decompiler;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jd.core.v1.ClassFileToJavaSourceDecompiler;
import org.jd.core.v1.api.loader.Loader;
import org.jd.core.v1.api.printer.Printer;
/**
* @author https://github.com/niumoo
* @date 2021/05/15
*/
public class JDCoreTest {
public static void main(String[] args) throws Exception {
JDCoreDecompiler jdCoreDecompiler = new JDCoreDecompiler();
Long time = jdCoreDecompiler.decompiler("decompiler.jar","jd_output_jar");
System.out.println(String.format("decompiler time: %dms", time));
}
}
class JDCoreDecompiler{
private ClassFileToJavaSourceDecompiler decompiler = new ClassFileToJavaSourceDecompiler();
// 存放字节码
private HashMap<String,byte[]> classByteMap = new HashMap<>();
/**
* 注意:没有考虑一个 Java 类编译出多个 Class 文件的情况。
*
* @param source
* @param target
* @return
* @throws Exception
*/
public Long decompiler(String source,String target) throws Exception {
long start = System.currentTimeMillis();
// 解压
archive(source);
for (String className : classByteMap.keySet()) {
String path = StringUtils.substringBeforeLast(className, "/");
String name = StringUtils.substringAfterLast(className, "/");
if (StringUtils.contains(name, "$")) {
name = StringUtils.substringAfterLast(name, "$");
}
name = StringUtils.replace(name, ".class", ".java");
decompiler.decompile(loader, printer, className);
String context = printer.toString();
Path targetPath = Paths.get(target + "/" + path + "/" + name);
if (!Files.exists(Paths.get(target + "/" + path))) {
Files.createDirectories(Paths.get(target + "/" + path));
}
Files.deleteIfExists(targetPath);
Files.createFile(targetPath);
Files.write(targetPath, context.getBytes());
}
return System.currentTimeMillis() - start;
}
private void archive(String path) throws IOException {
try (ZipFile archive = new JarFile(new File(path))) {
Enumeration<? extends ZipEntry> entries = archive.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
String name = entry.getName();
if (name.endsWith(".class")) {
byte[] bytes = null;
try (InputStream stream = archive.getInputStream(entry)) {
bytes = IOUtils.toByteArray(stream);
}
classByteMap.put(name, bytes);
}
}
}
}
}
private Loader loader = new Loader() {
@Override
public byte[] load(String internalName) {
return classByteMap.get(internalName);
}
@Override
public boolean canLoad(String internalName) {
return classByteMap.containsKey(internalName);
}
};
private Printer printer = new Printer() {
protected static final String TAB = " ";
protected static final String NEWLINE = "\n";
protected int indentationCount = 0;
protected StringBuilder sb = new StringBuilder();
@Override public String toString() {
String toString = sb.toString();
sb = new StringBuilder();
return toString;
}
@Override public void start(int maxLineNumber, int majorVersion, int minorVersion) {}
@Override public void end() {}
@Override public void printText(String text) { sb.append(text); }
@Override public void printNumericConstant(String constant) { sb.append(constant); }
@Override public void printStringConstant(String constant, String ownerInternalName) { sb.append(constant); }
@Override public void printKeyword(String keyword) { sb.append(keyword); }
@Override public void printDeclaration(int type, String internalTypeName, String name, String descriptor) { sb.append(name); }
@Override public void printReference(int type, String internalTypeName, String name, String descriptor, String ownerInternalName) { sb.append(name); }
@Override public void indent() { this.indentationCount++; }
@Override public void unindent() { this.indentationCount--; }
@Override public void startLine(int lineNumber) { for (int i=0; i<indentationCount; i++) sb.append(TAB); }
@Override public void endLine() { sb.append(NEWLINE); }
@Override public void extraLine(int count) { while (count-- > 0) sb.append(NEWLINE); }
@Override public void startMarker(int type) {}
@Override public void endMarker(int type) {}
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# JD-GUI
GitHub 地址:https://github.com/java-decompiler/jd-gui (opens new window)
JD-core 也提供了官方的 GUI 界面,需要的也可以直接下载尝试。
# Jadx
GitHub 地址:https://github.com/skylot/jadx (opens new window) Jadx 是一款可以反编译 JAR、APK、DEX、AAR、AAB、ZIP 文件的反编译工具,并且也配有 Jadx-gui 用于界面操作。 Jadx 使用 Grade 进行依赖管理,可以自行克隆仓库打包运行。
git clone https://github.com/skylot/jadx.git
cd jadx
./gradlew dist
# 查看帮助
./build/jadx/bin/jadx --help
jadx - dex to java decompiler, version: dev
usage: jadx [options] <input files> (.apk, .dex, .jar, .class, .smali, .zip, .aar, .arsc, .aab)
options:
-d, --output-dir - output directory
-ds, --output-dir-src - output directory for sources
-dr, --output-dir-res - output directory for resources
-r, --no-res - do not decode resources
-s, --no-src - do not decompile source code
--single-class - decompile a single class
--output-format - can be 'java' or 'json', default: java
-e, --export-gradle - save as android gradle project
-j, --threads-count - processing threads count, default: 6
--show-bad-code - show inconsistent code (incorrectly decompiled)
--no-imports - disable use of imports, always write entire package name
--no-debug-info - disable debug info
--add-debug-lines - add comments with debug line numbers if available
--no-inline-anonymous - disable anonymous classes inline
--no-replace-consts - don't replace constant value with matching constant field
--escape-unicode - escape non latin characters in strings (with \u)
--respect-bytecode-access-modifiers - don't change original access modifiers
--deobf - activate deobfuscation
--deobf-min - min length of name, renamed if shorter, default: 3
--deobf-max - max length of name, renamed if longer, default: 64
--deobf-cfg-file - deobfuscation map file, default: same dir and name as input file with '.jobf' extension
--deobf-rewrite-cfg - force to save deobfuscation map
--deobf-use-sourcename - use source file name as class name alias
--deobf-parse-kotlin-metadata - parse kotlin metadata to class and package names
--rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default)
--fs-case-sensitive - treat filesystem as case sensitive, false by default
--cfg - save methods control flow graph to dot file
--raw-cfg - save methods control flow graph (use raw instructions)
-f, --fallback - make simple dump (using goto instead of 'if', 'for', etc)
-v, --verbose - verbose output (set --log-level to DEBUG)
-q, --quiet - turn off output (set --log-level to QUIET)
--log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS
--version - print jadx version
-h, --help - print this help
Example:
jadx -d out classes.dex
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
根据 HELP 信息,如果想要反编译 decompiler.jar 到 out 文件夹。
./build/jadx/bin/jadx -d ./out ~/Desktop/decompiler.jar
INFO - loading ...
INFO - processing ...
INFO - doneress: 1143 of 1217 (93%)
2
3
4
# Fernflower
GitHub 地址:https://github.com/fesh0r/fernflower (opens new window) Fernflower 和 Jadx 一样使用 Grade 进行依赖管理,可以自行克隆仓库打包运行。
➜ fernflower-master ./gradlew build
BUILD SUCCESSFUL in 32s
4 actionable tasks: 4 executed
➜ fernflower-master java -jar build/libs/fernflower.jar
Usage: java -jar fernflower.jar [-<option>=<value>]* [<source>]+ <destination>
Example: java -jar fernflower.jar -dgs=true c:\my\source\ c:\my.jar d:\decompiled\
➜ fernflower-master mkdir out
➜ fernflower-master java -jar build/libs/fernflower.jar ~/Desktop/decompiler.jar ./out
INFO: Decompiling class com/strobel/assembler/metadata/ArrayTypeLoader
INFO: ... done
INFO: Decompiling class com/strobel/assembler/metadata/ParameterDefinition
INFO: ... done
INFO: Decompiling class com/strobel/assembler/metadata/MethodHandle
...
➜ fernflower-master ll out
total 1288
-rw-r--r-- 1 darcy staff 595K 5 16 17:47 decompiler.jar
➜ fernflower-master
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Fernflower 在反编译 JAR 包时,默认反编译的结果也是一个 JAR 包。Jad
# 反编译速度
到这里已经介绍了五款 Java 反编译工具了,那么在日常开发中我们应该使用哪一个呢?又或者在代码分析时我们又该选择哪一个呢?我想这两种情况的不同,使用时的关注点也是不同的。如果是日常使用,读读代码,我想应该是对可读性要求更高些,如果是大量的代码分析工作,那么可能反编译的速度和语法的支持上要求更高些。 为了能有一个简单的参考数据,我使用 JMH 微基准测试工具分别对这五款反编译工具进行了简单的测试,下面是一些测试结果。
测试环境
环境变量 | 描述 |
---|---|
处理器 | 2.6 GHz 六核Intel Core i7 |
内存 | 16 GB 2667 MHz DDR4 |
Java 版本 | JDK 14.0.2 |
测试方式 | JMH 基准测试。 |
待反编译 JAR 1 | procyon-compilertools-0.5.33.jar (1.5 MB) |
待反编译 JAR 2 | python2java4common-1.0.0-20180706.084921-1.jar (42 MB) |
反编译 JAR 1:procyon-compilertools-0.5.33.jar (1.5 MB)
Benchmark | Mode | Cnt | Score | Units |
---|---|---|---|---|
cfr | avgt | 10 | 6548.642 ± 363.502 | ms/op |
fernflower | avgt | 10 | 12699.147 ± 1081.539 | ms/op |
jdcore | avgt | 10 | 5728.621 ± 310.645 | ms/op |
procyon | avgt | 10 | 26776.125 ± 2651.081 | ms/op |
jadx | avgt | 10 | 7059.354 ± 323.351 | ms/op |
反编译 JAR 2: python2java4common-1.0.0-20180706.084921-1.jar (42 MB)
JAR 2 这个包是比较大的,是拿很多代码仓库合并到一起的,同时还有很多 Python 转 Java 生成的代码,理论上代码的复杂度会更高。
Benchmark | Cnt | Score |
---|---|---|
Cfr | 1 | 413838.826ms |
fernflower | 1 | 246819.168ms |
jdcore | 1 | Error |
procyon | 1 | 487647.181ms |
jadx | 1 | 505600.231ms |
# 语法支持和可读性
如果反编译后的代码需要自己看的话,那么可读性更好的代码更占优势,下面我写了一些代码,主要是 Java 8 及以下的代码语法和一些嵌套的流程控制,看看反编译后的效果如何。
package com.wdbyte.decompiler;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import org.benf.cfr.reader.util.functors.UnaryFunction;
/**
* @author https://www.wdbyte.com
* @date 2021/05/16
*/
public class HardCode <A, B> {
public HardCode(A a, B b) { }
public static void test(int... args) { }
public static void main(String... args) {
test(1, 2, 3, 4, 5, 6);
}
int byteAnd0() {
int b = 1;
int x = 0;
do {
b = (byte)((b ^ x));
} while (b++ < 10);
return b;
}
private void a(Integer i) {
a(i);
b(i);
c(i);
}
private void b(int i) {
a(i);
b(i);
c(i);
}
private void c(double d) {
c(d);
d(d);
}
private void d(Double d) {
c(d);
d(d);
}
private void e(Short s) {
b(s);
c(s);
e(s);
f(s);
}
private void f(short s) {
b(s);
c(s);
e(s);
f(s);
}
void test1(String path) {
try {
int x = 3;
} catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) { return; }
throw t;
} finally {
System.out.println("Fred");
if (path == null) { throw new IllegalStateException(); }
}
}
private final List<Integer> stuff = new ArrayList<>();{
stuff.add(1);
stuff.add(2);
}
public static int plus(boolean t, int a, int b) {
int c = t ? a : b;
return c;
}
// Lambda
Integer lambdaInvoker(int arg, UnaryFunction<Integer, Integer> fn) {
return fn.invoke(arg);
}
// Lambda
public int testLambda() {
return lambdaInvoker(3, x -> x + 1);
// return 1;
}
// Lambda
public Integer testLambda(List<Integer> stuff, int y, boolean b) {
return stuff.stream().filter(b ? x -> x > y : x -> x < 3).findFirst().orElse(null);
}
// stream
public static <Y extends Integer> void testStream(List<Y> list) {
IntStream s = list.stream()
.filter(x -> {
System.out.println(x);
return x.intValue() / 2 == 0;
})
.map(x -> (Integer)x+2)
.mapToInt(x -> x);
s.toArray();
}
// switch
public void testSwitch1(){
int i = 0;
switch(((Long)(i + 1L)) + "") {
case "1":
System.out.println("one");
}
}
// switch
public void testSwitch2(String string){
switch (string) {
case "apples":
System.out.println("apples");
break;
case "pears":
System.out.println("pears");
break;
}
}
// switch
public static void testSwitch3(int x) {
while (true) {
if (x < 5) {
switch ("test") {
case "okay":
continue;
default:
continue;
}
}
System.out.println("wow x2!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.wdbyte.decompiler;
import java.util.*;
import org.benf.cfr.reader.util.functors.*;
import java.util.stream.*;
public class HardCode<A, B>
{
private final List<Integer> stuff;
public HardCode(final A a, final B b) {
(this.stuff = new ArrayList<Integer>()).add(1);
this.stuff.add(2);
}
public static void test(final int... args) {
}
public static void main(final String... args) {
test(1, 2, 3, 4, 5, 6);
}
int byteAnd0() {
int b = 1;
final int x = 0;
do {
b = (byte)(b ^ x);
} while (b++ < 10);
return b;
}
private void a(final Integer i) {
this.a(i);
this.b(i);
this.c(i);
}
private void b(final int i) {
this.a(i);
this.b(i);
this.c(i);
}
private void c(final double d) {
this.c(d);
this.d(d);
}
private void d(final Double d) {
this.c(d);
this.d(d);
}
private void e(final Short s) {
this.b(s);
this.c(s);
this.e(s);
this.f(s);
}
private void f(final short s) {
this.b(s);
this.c(s);
this.e(s);
this.f(s);
}
void test1(final String path) {
try {}
catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) {
return;
}
throw t;
}
finally {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
}
public static int plus(final boolean t, final int a, final int b) {
final int c = t ? a : b;
return c;
}
Integer lambdaInvoker(final int arg, final UnaryFunction<Integer, Integer> fn) {
return (Integer)fn.invoke((Object)arg);
}
public int testLambda() {
return this.lambdaInvoker(3, (UnaryFunction<Integer, Integer>)(x -> x + 1));
}
public Integer testLambda(final List<Integer> stuff, final int y, final boolean b) {
return stuff.stream().filter(b ? (x -> x > y) : (x -> x < 3)).findFirst().orElse(null);
}
public static <Y extends Integer> void testStream(final List<Y> list) {
final IntStream s = list.stream().filter(x -> {
System.out.println(x);
return x / 2 == 0;
}).map(x -> x + 2).mapToInt(x -> x);
s.toArray();
}
public void testSwitch1() {
final int i = 0;
final String string = (Object)(i + 1L) + "";
switch (string) {
case "1": {
System.out.println("one");
break;
}
}
}
public void testSwitch2(final String string) {
switch (string) {
case "apples": {
System.out.println("apples");
break;
}
case "pears": {
System.out.println("pears");
break;
}
}
}
public static void testSwitch3(final int x) {
while (true) {
if (x < 5) {
final String s = "test";
switch (s) {
case "okay": {
continue;
}
default: {
continue;
}
}
}
else {
System.out.println("wow x2!");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/*
* Decompiled with CFR 0.151.
*/
package com.wdbyte.decompiler;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import org.benf.cfr.reader.util.functors.UnaryFunction;
public class HardCode<A, B> {
private final List<Integer> stuff = new ArrayList<Integer>();
public HardCode(A a, B b) {
this.stuff.add(1);
this.stuff.add(2);
}
public static void test(int ... args) {
}
public static void main(String ... args) {
HardCode.test(1, 2, 3, 4, 5, 6);
}
int byteAnd0() {
byte by;
byte b = 1;
boolean x = false;
do {
by = b = (byte)(b ^ (x ? 1 : 0));
b = (byte)(b + 1);
} while (by < 10);
return b;
}
private void a(Integer i) {
this.a(i);
this.b(i);
this.c(i.intValue());
}
private void b(int i) {
this.a(i);
this.b(i);
this.c(i);
}
private void c(double d) {
this.c(d);
this.d(d);
}
private void d(Double d) {
this.c(d);
this.d(d);
}
private void e(Short s) {
this.b(s.shortValue());
this.c(s.shortValue());
this.e(s);
this.f(s);
}
private void f(short s) {
this.b(s);
this.c(s);
this.e(s);
this.f(s);
}
void test1(String path) {
try {
int n = 3;
}
catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) {
return;
}
throw t;
}
finally {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
}
public static int plus(boolean t, int a, int b) {
int c = t ? a : b;
return c;
}
Integer lambdaInvoker(int arg, UnaryFunction<Integer, Integer> fn) {
return fn.invoke(arg);
}
public int testLambda() {
return this.lambdaInvoker(3, x -> x + 1);
}
public Integer testLambda(List<Integer> stuff, int y, boolean b) {
return stuff.stream().filter(b ? x -> x > y : x -> x < 3).findFirst().orElse(null);
}
public static <Y extends Integer> void testStream(List<Y> list) {
IntStream s = list.stream().filter(x -> {
System.out.println(x);
return x / 2 == 0;
}).map(x -> x + 2).mapToInt(x -> x);
s.toArray();
}
public void testSwitch1() {
int i = 0;
switch (Long.valueOf((long)i + 1L) + "") {
case "1": {
System.out.println("one");
}
}
}
public void testSwitch2(String string) {
switch (string) {
case "apples": {
System.out.println("apples");
break;
}
case "pears": {
System.out.println("pears");
}
}
}
public static void testSwitch3(int x) {
block6: while (true) {
if (x < 5) {
switch ("test") {
case "okay": {
continue block6;
}
}
continue;
}
System.out.println("wow x2!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import com.wdbyte.decompiler.HardCode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import org.benf.cfr.reader.util.functors.UnaryFunction;
public class HardCode<A, B> {
private final List<Integer> stuff;
public HardCode(A a, B b) {
this.stuff = new ArrayList<>();
this.stuff.add(Integer.valueOf(1));
this.stuff.add(Integer.valueOf(2));
}
public static void test(int... args) {}
public static void main(String... args) {
test(new int[] { 1, 2, 3, 4, 5, 6 });
}
int byteAnd0() {
int b = 1;
int x = 0;
while (true) {
b = (byte)(b ^ x);
if (b++ >= 10)
return b;
}
}
private void a(Integer i) {
a(i);
b(i.intValue());
c(i.intValue());
}
private void b(int i) {
a(Integer.valueOf(i));
b(i);
c(i);
}
private void c(double d) {
c(d);
d(Double.valueOf(d));
}
private void d(Double d) {
c(d.doubleValue());
d(d);
}
private void e(Short s) {
b(s.shortValue());
c(s.shortValue());
e(s);
f(s.shortValue());
}
private void f(short s) {
b(s);
c(s);
e(Short.valueOf(s));
f(s);
}
void test1(String path) {
try {
byte b = 3;
} catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null)
return;
throw t;
} finally {
System.out.println("Fred");
if (path == null)
throw new IllegalStateException();
}
}
public static int plus(boolean t, int a, int b) {
int c = t ? a : b;
return c;
}
Integer lambdaInvoker(int arg, UnaryFunction<Integer, Integer> fn) {
return (Integer)fn.invoke(Integer.valueOf(arg));
}
public int testLambda() {
return lambdaInvoker(3, x -> Integer.valueOf(x.intValue() + 1)).intValue();
}
public Integer testLambda(List<Integer> stuff, int y, boolean b) {
return stuff.stream().filter(b ? (x -> (x.intValue() > y)) : (x -> (x.intValue() < 3))).findFirst().orElse(null);
}
public static <Y extends Integer> void testStream(List<Y> list) {
IntStream s = list.stream().filter(x -> {
System.out.println(x);
return (x.intValue() / 2 == 0);
}).map(x -> Integer.valueOf(x.intValue() + 2)).mapToInt(x -> x.intValue());
s.toArray();
}
public void testSwitch1() {
int i = 0;
switch (Long.valueOf(i + 1L) + "") {
case "1":
System.out.println("one");
break;
}
}
public void testSwitch2(String string) {
switch (string) {
case "apples":
System.out.println("apples");
break;
case "pears":
System.out.println("pears");
break;
}
}
public static void testSwitch3(int x) {
while (true) {
while (x < 5) {
switch ("test") {
case "okay":
break;
}
}
System.out.println("wow x2!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package com.wdbyte.decompiler;
import java.util.ArrayList;
import java.util.List;
import org.benf.cfr.reader.util.functors.UnaryFunction;
/* JADX ERROR: Class process error: JadxRuntimeException
jadx.core.utils.exceptions.JadxRuntimeException: Code generation error after restart
at jadx.core.codegen.CodeGen.wrapCodeGen(CodeGen.java:52)
at jadx.core.codegen.CodeGen.generateJavaCode(CodeGen.java:34)
at jadx.core.codegen.CodeGen.generate(CodeGen.java:22)
at jadx.core.ProcessClass.process(ProcessClass.java:64)
at jadx.core.ProcessClass.generateCode(ProcessClass.java:88)
at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:258)
at jadx.core.dex.nodes.ClassNode.decompile(ClassNode.java:221)
Caused by: jadx.core.utils.exceptions.JadxRuntimeException: Method generation error
at jadx.core.codegen.ClassGen.addMethod(ClassGen.java:298)
at jadx.core.codegen.ClassGen.lambda$addInnerClsAndMethods$2(ClassGen.java:264)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
Caused by: jadx.core.utils.exceptions.CodegenException: Error generate insn: 0x001c: INVOKE
(wrap: java.util.stream.IntStream : 0x0018: INVOKE (r0v0 's' java.util.stream.IntStream) =
(wrap: java.util.stream.Stream<R> : 0x0010: INVOKE (r1v2 java.util.stream.Stream<R>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0008: INVOKE (r1v1 java.util.stream.Stream<Y extends java.lang.Integer>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0000: INVOKE (r1v0 java.util.stream.Stream<Y extends java.lang.Integer>) = (r3v0 'list' java.util.List<Y extends java.lang.Integer> A[D('list' java.util.List<Y extends java.lang.Integer>)]) type: INTERFACE call: java.util.List.stream():java.util.stream.Stream)
(wrap: java.util.function.Predicate<? super Y extends java.lang.Integer> : 0x0007: MOVE_RESULT (r2v0 java.util.function.Predicate<? super Y extends java.lang.Integer>) = )
type: INTERFACE call: java.util.stream.Stream.filter(java.util.function.Predicate):java.util.stream.Stream)
(wrap: java.util.function.Function<? super Y extends java.lang.Integer, ? extends R> : 0x000f: MOVE_RESULT (r2v1 java.util.function.Function<? super Y extends java.lang.Integer, ? extends R>) = )
type: INTERFACE call: java.util.stream.Stream.map(java.util.function.Function):java.util.stream.Stream)
(wrap: java.util.function.ToIntFunction<? super R> : 0x0017: MOVE_RESULT (r2v2 java.util.function.ToIntFunction<? super R>) = )
type: INTERFACE call: java.util.stream.Stream.mapToInt(java.util.function.ToIntFunction):java.util.stream.IntStream)
type: INTERFACE call: java.util.stream.IntStream.toArray():int[] in method: com.wdbyte.decompiler.HardCode.testStream(java.util.List<Y extends java.lang.Integer>):void, file: /var/folders/z4/q_08v96n28j2v3rgybvhytvr0000gp/T/jadx-10342693507538465510/classes.dex
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:255)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:217)
at jadx.core.codegen.RegionGen.makeSimpleBlock(RegionGen.java:110)
at jadx.core.codegen.RegionGen.makeRegion(RegionGen.java:56)
at jadx.core.codegen.RegionGen.makeSimpleRegion(RegionGen.java:93)
at jadx.core.codegen.RegionGen.makeRegion(RegionGen.java:59)
at jadx.core.codegen.MethodGen.addRegionInsns(MethodGen.java:244)
at jadx.core.codegen.MethodGen.addInstructions(MethodGen.java:237)
at jadx.core.codegen.ClassGen.addMethodCode(ClassGen.java:342)
at jadx.core.codegen.ClassGen.addMethod(ClassGen.java:295)
... 5 more
Caused by: jadx.core.utils.exceptions.CodegenException: Error generate insn: 0x0018: INVOKE (r0v0 's' java.util.stream.IntStream) =
(wrap: java.util.stream.Stream<R> : 0x0010: INVOKE (r1v2 java.util.stream.Stream<R>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0008: INVOKE (r1v1 java.util.stream.Stream<Y extends java.lang.Integer>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0000: INVOKE (r1v0 java.util.stream.Stream<Y extends java.lang.Integer>) = (r3v0 'list' java.util.List<Y extends java.lang.Integer> A[D('list' java.util.List<Y extends java.lang.Integer>)]) type: INTERFACE call: java.util.List.stream():java.util.stream.Stream)
(wrap: java.util.function.Predicate<? super Y extends java.lang.Integer> : 0x0007: MOVE_RESULT (r2v0 java.util.function.Predicate<? super Y extends java.lang.Integer>) = )
type: INTERFACE call: java.util.stream.Stream.filter(java.util.function.Predicate):java.util.stream.Stream)
(wrap: java.util.function.Function<? super Y extends java.lang.Integer, ? extends R> : 0x000f: MOVE_RESULT (r2v1 java.util.function.Function<? super Y extends java.lang.Integer, ? extends R>) = )
type: INTERFACE call: java.util.stream.Stream.map(java.util.function.Function):java.util.stream.Stream)
(wrap: java.util.function.ToIntFunction<? super R> : 0x0017: MOVE_RESULT (r2v2 java.util.function.ToIntFunction<? super R>) = )
type: INTERFACE call: java.util.stream.Stream.mapToInt(java.util.function.ToIntFunction):java.util.stream.IntStream in method: com.wdbyte.decompiler.HardCode.testStream(java.util.List<Y extends java.lang.Integer>):void, file: /var/folders/z4/q_08v96n28j2v3rgybvhytvr0000gp/T/jadx-10342693507538465510/classes.dex
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:255)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:249)
... 14 more
Caused by: jadx.core.utils.exceptions.CodegenException: Error generate insn: 0x0010: INVOKE (r1v2 java.util.stream.Stream<R>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0008: INVOKE (r1v1 java.util.stream.Stream<Y extends java.lang.Integer>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0000: INVOKE (r1v0 java.util.stream.Stream<Y extends java.lang.Integer>) = (r3v0 'list' java.util.List<Y extends java.lang.Integer> A[D('list' java.util.List<Y extends java.lang.Integer>)]) type: INTERFACE call: java.util.List.stream():java.util.stream.Stream)
(wrap: java.util.function.Predicate<? super Y extends java.lang.Integer> : 0x0007: MOVE_RESULT (r2v0 java.util.function.Predicate<? super Y extends java.lang.Integer>) = )
type: INTERFACE call: java.util.stream.Stream.filter(java.util.function.Predicate):java.util.stream.Stream)
(wrap: java.util.function.Function<? super Y extends java.lang.Integer, ? extends R> : 0x000f: MOVE_RESULT (r2v1 java.util.function.Function<? super Y extends java.lang.Integer, ? extends R>) = )
type: INTERFACE call: java.util.stream.Stream.map(java.util.function.Function):java.util.stream.Stream in method: com.wdbyte.decompiler.HardCode.testStream(java.util.List<Y extends java.lang.Integer>):void, file: /var/folders/z4/q_08v96n28j2v3rgybvhytvr0000gp/T/jadx-10342693507538465510/classes.dex
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:255)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
... 20 more
Caused by: jadx.core.utils.exceptions.CodegenException: Error generate insn: 0x0008: INVOKE (r1v1 java.util.stream.Stream<Y extends java.lang.Integer>) =
(wrap: java.util.stream.Stream<Y extends java.lang.Integer> : 0x0000: INVOKE (r1v0 java.util.stream.Stream<Y extends java.lang.Integer>) = (r3v0 'list' java.util.List<Y extends java.lang.Integer> A[D('list' java.util.List<Y extends java.lang.Integer>)]) type: INTERFACE call: java.util.List.stream():java.util.stream.Stream)
(wrap: java.util.function.Predicate<? super Y extends java.lang.Integer> : 0x0007: MOVE_RESULT (r2v0 java.util.function.Predicate<? super Y extends java.lang.Integer>) = )
type: INTERFACE call: java.util.stream.Stream.filter(java.util.function.Predicate):java.util.stream.Stream in method: com.wdbyte.decompiler.HardCode.testStream(java.util.List<Y extends java.lang.Integer>):void, file: /var/folders/z4/q_08v96n28j2v3rgybvhytvr0000gp/T/jadx-10342693507538465510/classes.dex
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:255)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
... 26 more
Caused by: jadx.core.utils.exceptions.CodegenException: Error generate insn: 0x0007: MOVE_RESULT (r2v0 java.util.function.Predicate<? super Y extends java.lang.Integer>) = in method: com.wdbyte.decompiler.HardCode.testStream(java.util.List<Y extends java.lang.Integer>):void, file: /var/folders/z4/q_08v96n28j2v3rgybvhytvr0000gp/T/jadx-10342693507538465510/classes.dex
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:255)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.generateMethodArguments(InsnGen.java:806)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:746)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
... 32 more
Caused by: jadx.core.utils.exceptions.CodegenException: MOVE_RESULT instruction can be used only in fallback mode
at jadx.core.codegen.InsnGen.fallbackOnlyInsn(InsnGen.java:604)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:542)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
... 38 more
*/
public class HardCode<A, B> {
private final List<Integer> stuff = new ArrayList();
public HardCode(A a, B b) {
this.stuff.add(1);
this.stuff.add(2);
}
public static void test(int... args) {
}
public static void main(String... args) {
test(1, 2, 3, 4, 5, 6);
}
/* access modifiers changed from: package-private */
public int byteAnd0() {
int b = 1;
while (true) {
int b2 = (byte) (b ^ 0);
int b3 = b2 + 1;
if (b2 >= 10) {
return b3;
}
b = b3;
}
}
private void a(Integer i) {
a(i);
b(i.intValue());
c((double) i.intValue());
}
private void b(int i) {
a(Integer.valueOf(i));
b(i);
c((double) i);
}
private void c(double d) {
c(d);
d(Double.valueOf(d));
}
private void d(Double d) {
c(d.doubleValue());
d(d);
}
private void e(Short s) {
b(s.shortValue());
c((double) s.shortValue());
e(s);
f(s.shortValue());
}
private void f(short s) {
b(s);
c((double) s);
e(Short.valueOf(s));
f(s);
}
/* access modifiers changed from: package-private */
public void test1(String path) {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
public static int plus(boolean t, int a, int b) {
return t ? a : b;
}
/* access modifiers changed from: package-private */
public Integer lambdaInvoker(int arg, UnaryFunction<Integer, Integer> fn) {
return (Integer) fn.invoke(Integer.valueOf(arg));
}
public int testLambda() {
/*
r2 = this;
r0 = 3
r1 = move-result
java.lang.Integer r0 = r2.lambdaInvoker(r0, r1)
int r0 = r0.intValue()
return r0
*/
throw new UnsupportedOperationException("Method not decompiled: com.wdbyte.decompiler.HardCode.testLambda():int");
}
private static /* synthetic */ boolean lambda$testLambda$1(int y, Integer x) {
return x.intValue() > y;
}
private static /* synthetic */ boolean lambda$testLambda$2(Integer x) {
return x.intValue() < 3;
}
public java.lang.Integer testLambda(java.util.List<java.lang.Integer> r3, int r4, boolean r5) {
/*
r2 = this;
java.util.stream.Stream r1 = r3.stream()
if (r5 == 0) goto L_0x001a
r0 = move-result
L_0x000a:
java.util.stream.Stream r0 = r1.filter(r0)
java.util.Optional r0 = r0.findFirst()
r1 = 0
java.lang.Object r0 = r0.orElse(r1)
java.lang.Integer r0 = (java.lang.Integer) r0
return r0
L_?:
r0 = move-result
goto L_0x000a
*/
throw new UnsupportedOperationException("Method not decompiled: com.wdbyte.decompiler.HardCode.testLambda(java.util.List, int, boolean):java.lang.Integer");
}
/* JADX ERROR: MOVE_RESULT instruction can be used only in fallback mode
jadx.core.utils.exceptions.CodegenException: MOVE_RESULT instruction can be used only in fallback mode
at jadx.core.codegen.InsnGen.fallbackOnlyInsn(InsnGen.java:604)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:542)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.generateMethodArguments(InsnGen.java:806)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:746)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:230)
at jadx.core.codegen.InsnGen.addWrappedArg(InsnGen.java:119)
at jadx.core.codegen.InsnGen.addArg(InsnGen.java:103)
at jadx.core.codegen.InsnGen.addArgDot(InsnGen.java:87)
at jadx.core.codegen.InsnGen.makeInvoke(InsnGen.java:715)
at jadx.core.codegen.InsnGen.makeInsnBody(InsnGen.java:367)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:249)
at jadx.core.codegen.InsnGen.makeInsn(InsnGen.java:217)
at jadx.core.codegen.RegionGen.makeSimpleBlock(RegionGen.java:110)
at jadx.core.codegen.RegionGen.makeRegion(RegionGen.java:56)
at jadx.core.codegen.RegionGen.makeSimpleRegion(RegionGen.java:93)
at jadx.core.codegen.RegionGen.makeRegion(RegionGen.java:59)
at jadx.core.codegen.MethodGen.addRegionInsns(MethodGen.java:244)
at jadx.core.codegen.MethodGen.addInstructions(MethodGen.java:237)
at jadx.core.codegen.ClassGen.addMethodCode(ClassGen.java:342)
at jadx.core.codegen.ClassGen.addMethod(ClassGen.java:295)
at jadx.core.codegen.ClassGen.lambda$addInnerClsAndMethods$2(ClassGen.java:264)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:395)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
*/
public static <Y extends java.lang.Integer> void testStream(java.util.List<Y> r3) {
/*
java.util.stream.Stream r1 = r3.stream()
r2 = move-result
java.util.stream.Stream r1 = r1.filter(r2)
r2 = move-result
java.util.stream.Stream r1 = r1.map(r2)
r2 = move-result
java.util.stream.IntStream r0 = r1.mapToInt(r2)
r0.toArray()
return
*/
throw new UnsupportedOperationException("Method not decompiled: com.wdbyte.decompiler.HardCode.testStream(java.util.List):void");
}
private static /* synthetic */ boolean lambda$testStream$3(Integer x) {
System.out.println(x);
return x.intValue() / 2 == 0;
}
public void testSwitch1() {
String str = Long.valueOf(((long) 0) + 1) + "";
char c = 65535;
switch (str.hashCode()) {
case 49:
if (str.equals("1")) {
c = 0;
break;
}
break;
}
switch (c) {
case 0:
System.out.println("one");
return;
default:
return;
}
}
public void testSwitch2(String string) {
char c = 65535;
switch (string.hashCode()) {
case -1411061671:
if (string.equals("apples")) {
c = 0;
break;
}
break;
case 106540109:
if (string.equals("pears")) {
c = 1;
break;
}
break;
}
switch (c) {
case 0:
System.out.println("apples");
return;
case 1:
System.out.println("pears");
return;
default:
return;
}
}
public static void testSwitch3(int x) {
while (true) {
if (x < 5) {
char c = 65535;
switch ("test".hashCode()) {
case 3412756:
if ("test".equals("okay")) {
c = 0;
break;
}
break;
}
switch (c) {
}
} else {
System.out.println("wow x2!");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
package com.wdbyte.decompiler;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.IntStream;
import org.benf.cfr.reader.util.functors.UnaryFunction;
public class HardCode {
private final List stuff = new ArrayList();
public HardCode(Object a, Object b) {
this.stuff.add(1);
this.stuff.add(2);
}
public static void test(int... args) {
}
public static void main(String... args) {
test(1, 2, 3, 4, 5, 6);
}
int byteAnd0() {
int b = 1;
byte x = 0;
byte var10000;
do {
int b = (byte)(b ^ x);
var10000 = b;
b = b + 1;
} while(var10000 < 10);
return b;
}
private void a(Integer i) {
this.a(i);
this.b(i);
this.c((double)i);
}
private void b(int i) {
this.a(i);
this.b(i);
this.c((double)i);
}
private void c(double d) {
this.c(d);
this.d(d);
}
private void d(Double d) {
this.c(d);
this.d(d);
}
private void e(Short s) {
this.b(s);
this.c((double)s);
this.e(s);
this.f(s);
}
private void f(short s) {
this.b(s);
this.c((double)s);
this.e(s);
this.f(s);
}
void test1(String path) {
try {
boolean var2 = true;
return;
} catch (NullPointerException var6) {
System.out.println("File Not found");
if (path != null) {
throw var6;
}
} finally {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
}
public static int plus(boolean t, int a, int b) {
int c = t ? a : b;
return c;
}
Integer lambdaInvoker(int arg, UnaryFunction fn) {
return (Integer)fn.invoke(arg);
}
public int testLambda() {
return this.lambdaInvoker(3, (x) -> {
return x + 1;
});
}
public Integer testLambda(List stuff, int y, boolean b) {
return (Integer)stuff.stream().filter(b ? (x) -> {
return x > y;
} : (x) -> {
return x < 3;
}).findFirst().orElse((Object)null);
}
public static void testStream(List list) {
IntStream s = list.stream().filter((x) -> {
System.out.println(x);
return x / 2 == 0;
}).map((x) -> {
return x + 2;
}).mapToInt((x) -> {
return x;
});
s.toArray();
}
public void testSwitch1() {
int i = 0;
String var2 = (long)i + 1L + "";
byte var3 = -1;
switch(var2.hashCode()) {
case 49:
if (var2.equals("1")) {
var3 = 0;
}
default:
switch(var3) {
case 0:
System.out.println("one");
default:
}
}
}
public void testSwitch2(String string) {
byte var3 = -1;
switch(string.hashCode()) {
case -1411061671:
if (string.equals("apples")) {
var3 = 0;
}
break;
case 106540109:
if (string.equals("pears")) {
var3 = 1;
}
}
switch(var3) {
case 0:
System.out.println("apples");
break;
case 1:
System.out.println("pears");
}
}
public static void testSwitch3(int x) {
while(true) {
if (x < 5) {
String var1 = "test";
byte var2 = -1;
switch(var1.hashCode()) {
case 3412756:
if (var1.equals("okay")) {
var2 = 0;
}
default:
switch(var2) {
case 0:
}
}
} else {
System.out.println("wow x2!");
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// Make sure to add code blocks to your code group
# Procyon
看到 Procyon 的反编译结果,还是比较吃惊的,在正常反编译的情况下,反编译后的代码基本上都是原汁原味。唯一一处反编译后和源码语法上有变化的地方,是一个集合的初始化操作略有不同。
// 源码
public HardCode(A a, B b) { }
private final List<Integer> stuff = new ArrayList<>();{
stuff.add(1);
stuff.add(2);
}
2
3
4
5
6
// Procyon 反编译
private final List<Integer> stuff;
public HardCode(final A a, final B b) {
(this.stuff = new ArrayList<Integer>()).add(1);
this.stuff.add(2);
}
2
3
4
5
6
7
// Make sure to add code blocks to your code group
而其他部分代码, 比如装箱拆箱,Switch 语法,Lambda 表达式,流式操作以及流程控制等,几乎完全一致,阅读没有障碍。
装箱拆箱操作反编译后完全一致,没有多余的类型转换代码。
// 源码
private void a(Integer i) {
a(i);
b(i);
c(i);
}
private void b(int i) {
a(i);
b(i);
c(i);
}
private void c(double d) {
c(d);
d(d);
}
private void d(Double d) {
c(d);
d(d);
}
private void e(Short s) {
b(s);
c(s);
e(s);
f(s);
}
private void f(short s) {
b(s);
c(s);
e(s);
f(s);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Procyon 反编译
private void a(final Integer i) {
this.a(i);
this.b(i);
this.c(i);
}
private void b(final int i) {
this.a(i);
this.b(i);
this.c(i);
}
private void c(final double d) {
this.c(d);
this.d(d);
}
private void d(final Double d) {
this.c(d);
this.d(d);
}
private void e(final Short s) {
this.b(s);
this.c(s);
this.e(s);
this.f(s);
}
private void f(final short s) {
this.b(s);
this.c(s);
this.e(s);
this.f(s);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// Make sure to add code blocks to your code group
Switch 部分也是一致,流程控制部分也没有变化。
// 源码 switch
public void testSwitch1(){
int i = 0;
switch(((Long)(i + 1L)) + "") {
case "1":
System.out.println("one");
}
}
public void testSwitch2(String string){
switch (string) {
case "apples":
System.out.println("apples");
break;
case "pears":
System.out.println("pears");
break;
}
}
public static void testSwitch3(int x) {
while (true) {
if (x < 5) {
switch ("test") {
case "okay":
continue;
default:
continue;
}
}
System.out.println("wow x2!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Procyon 反编译
public void testSwitch1() {
final int i = 0;
final String string = (Object)(i + 1L) + "";
switch (string) {
case "1": {
System.out.println("one");
break;
}
}
}
public void testSwitch2(final String string) {
switch (string) {
case "apples": {
System.out.println("apples");
break;
}
case "pears": {
System.out.println("pears");
break;
}
}
}
public static void testSwitch3(final int x) {
while (true) {
if (x < 5) {
final String s = "test";
switch (s) {
case "okay": {
continue;
}
default: {
continue;
}
}
}
else {
System.out.println("wow x2!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// Make sure to add code blocks to your code group
Lambda 表达式和流式操作完全一致。
// 源码
// Lambda
public Integer testLambda(List<Integer> stuff, int y, boolean b) {
return stuff.stream().filter(b ? x -> x > y : x -> x < 3).findFirst().orElse(null);
}
// stream
public static <Y extends Integer> void testStream(List<Y> list) {
IntStream s = list.stream()
.filter(x -> {
System.out.println(x);
return x.intValue() / 2 == 0;
})
.map(x -> (Integer)x+2)
.mapToInt(x -> x);
s.toArray();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Procyon 反编译
public Integer testLambda(final List<Integer> stuff, final int y, final boolean b) {
return stuff.stream().filter(b ? (x -> x > y) : (x -> x < 3)).findFirst().orElse(null);
}
public static <Y extends Integer> void testStream(final List<Y> list) {
final IntStream s = list.stream().filter(x -> {
System.out.println(x);
return x / 2 == 0;
}).map(x -> x + 2).mapToInt(x -> x);
s.toArray();
}
2
3
4
5
6
7
8
9
10
11
12
// Make sure to add code blocks to your code group
流程控制,反编译后发现丢失了无意义的代码部分,阅读来说并无障碍。
// 源码
void test1(String path) {
try {
int x = 3;
} catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) { return; }
throw t;
} finally {
System.out.println("Fred");
if (path == null) { throw new IllegalStateException(); }
}
}
2
3
4
5
6
7
8
9
10
11
12
13
// Procyon 反编译
void test1(final String path) {
try {}
catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) {
return;
}
throw t;
}
finally {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Make sure to add code blocks to your code group
鉴于代码篇幅,下面几种的反编译结果的对比只会列出不同之处,相同之处会直接跳过。
# CFR
CFR 的反编译结果多出了类型转换部分,个人来看没有 Procyon 那么原汁原味,不过也算是十分优秀,测试案例中唯一不满意的地方是对 while continue
的处理。
// CFR 反编译结果
// 装箱拆箱
private void e(Short s) {
this.b(s.shortValue()); // 装箱拆箱多出了类型转换部分。
this.c(s.shortValue()); // 装箱拆箱多出了类型转换部分。
this.e(s);
this.f(s);
}
// 流程控制
void test1(String path) {
try {
int n = 3;// 流程控制反编译结果十分满意,原汁原味,甚至此处的无意思代码都保留了。
}
catch (NullPointerException t) {
System.out.println("File Not found");
if (path == null) {
return;
}
throw t;
}
finally {
System.out.println("Fred");
if (path == null) {
throw new IllegalStateException();
}
}
}
// Lambda 和 Stream 操作完全一致,不提。
// switch 处,反编译后功能一致,但是流程控制有所更改。
public static void testSwitch3(int x) {
block6: while (true) { // 源码中只有 while(true),反编译后多了 block6
if (x < 5) {
switch ("test") {
case "okay": {
continue block6; // 多了 block6
}
}
continue;
}
System.out.println("wow x2!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# JD-Core
JD-Core 和 CFR 一样,对于装箱拆箱操作,反编译后不再一致,多了类型转换部分,而且自动优化了数据类型。个人感觉,如果是反编译后自己阅读,通篇的数据类型的转换优化影响还是挺大的。
// JD-Core 反编译
private void d(Double d) {
c(d.doubleValue()); // 新增了数据类型转换
d(d);
}
private void e(Short s) {
b(s.shortValue()); // 新增了数据类型转换
c(s.shortValue()); // 新增了数据类型转换
e(s);
f(s.shortValue()); // 新增了数据类型转换
}
private void f(short s) {
b(s);
c(s);
e(Short.valueOf(s)); // 新增了数据类型转换
f(s);
}
// Stream 操作中,也自动优化了数据类型转换,阅读起来比较累。
public static <Y extends Integer> void testStream(List<Y> list) {
IntStream s = list.stream().filter(x -> {
System.out.println(x);
return (x.intValue() / 2 == 0);
}).map(x -> Integer.valueOf(x.intValue() + 2)).mapToInt(x -> x.intValue());
s.toArray();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# Jadx
首先 Jadx 在反编译测试代码时,报出了错误,反编译的结果里也有提示不能反编 Lambda 和 Stream 操作,反编译结果中变量名称杂乱无章,流程控制几乎阵亡,如果你想反编译后生物肉眼阅读,Jadx 肯定不是一个好选择。
// Jadx 反编译
private void e(Short s) {
b(s.shortValue());// 新增了数据类型转换
c((double) s.shortValue());// 新增了数据类型转换
e(s);
f(s.shortValue());// 新增了数据类型转换
}
private void f(short s) {
b(s);
c((double) s);// 新增了数据类型转换
e(Short.valueOf(s));// 新增了数据类型转换
f(s);
}
public int testLambda() { // testLambda 反编译失败
/*
r2 = this;
r0 = 3
r1 = move-result
java.lang.Integer r0 = r2.lambdaInvoker(r0, r1)
int r0 = r0.intValue()
return r0
*/
throw new UnsupportedOperationException("Method not decompiled: com.wdbyte.decompiler.HardCode.testLambda():int");
}
// Stream 反编译失败
public static <Y extends java.lang.Integer> void testStream(java.util.List<Y> r3) {
/*
java.util.stream.Stream r1 = r3.stream()
r2 = move-result
java.util.stream.Stream r1 = r1.filter(r2)
r2 = move-result
java.util.stream.Stream r1 = r1.map(r2)
r2 = move-result
java.util.stream.IntStream r0 = r1.mapToInt(r2)
r0.toArray()
return
*/
throw new UnsupportedOperationException("Method not decompiled: com.wdbyte.decompiler.HardCode.testStream(java.util.List):void");
}
public void testSwitch2(String string) { // switch 操作无法正常阅读,和源码出入较大。
char c = 65535;
switch (string.hashCode()) {
case -1411061671:
if (string.equals("apples")) {
c = 0;
break;
}
break;
case 106540109:
if (string.equals("pears")) {
c = 1;
break;
}
break;
}
switch (c) {
case 0:
System.out.println("apples");
return;
case 1:
System.out.println("pears");
return;
default:
return;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# Fernflower
Fernflower 的反编译结果总体上还是不错的,不过也有不足,它对变量名称的指定,以及 Switch 字符串时的反编译结果不够理想。
//反编译后变量命名不利于阅读,有很多 var 变量
int byteAnd0() {
int b = 1;
byte x = 0;
byte var10000;
do {
int b = (byte)(b ^ x);
var10000 = b;
b = b + 1;
} while(var10000 < 10);
return b;
}
// switch 反编译结果使用了hashCode
public static void testSwitch3(int x) {
while(true) {
if (x < 5) {
String var1 = "test";
byte var2 = -1;
switch(var1.hashCode()) {
case 3412756:
if (var1.equals("okay")) {
var2 = 0;
}
default:
switch(var2) {
case 0:
}
}
} else {
System.out.println("wow x2!");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 总结
五种反编译工具比较下来,结合反编译速度和代码可读性测试,看起来 CFR 工具胜出,Procyon 紧随其后。CFR 在速度上不落下风,在反编译的代码可读性上,是最好的,主要体现在反编译后的变量命名、装箱拆箱、类型转换,流程控制上,以及对 Lambda 表达式、Stream 流式操作和 Switch 的语法支持上,都非常优秀。根据 CFR 官方介绍,已经支持到 Java 14 语法,而且截止写这篇测试文章时,CFR 最新提交代码时间实在 11 小时之前,更新速度很快。
文章中部分代码已经上传 GitHub :github.com/niumoo/lab-notes/tree/master/java-decompiler (opens new window)
提示:评论前请刷新页面,否则评论的可能不是当前文章。