From 4e34d746b04f026e0808a31d1bdd92bbb517c384 Mon Sep 17 00:00:00 2001 From: rat Date: Fri, 12 Jan 2024 18:45:56 -0600 Subject: [PATCH 1/4] Added Vineflower decompiler, refactored DecompilerResult and JvmDecompiler --- dependencies.gradle | 2 + recaf-core/build.gradle | 1 + .../recaf/config/BasicConfigContainer.java | 1 + .../decompile/AbstractDecompiler.java | 3 + .../decompile/AbstractJvmDecompiler.java | 10 +- .../decompile/BaseDecompilerConfig.java | 31 ++++ .../services/decompile/DecompileResult.java | 48 +++++- .../recaf/services/decompile/Decompiler.java | 4 + .../services/decompile/DecompilerConfig.java | 12 +- .../services/decompile/DecompilerManager.java | 24 ++- .../services/decompile/JvmDecompiler.java | 13 +- .../decompile/NoopAndroidDecompiler.java | 2 +- .../decompile/NoopDecompilerConfig.java | 4 +- .../services/decompile/NoopJvmDecompiler.java | 6 +- .../services/decompile/cfr/CfrConfig.java | 16 +- .../services/decompile/cfr/CfrDecompiler.java | 13 +- .../decompile/procyon/ProcyonConfig.java | 17 +-- .../decompile/procyon/ProcyonDecompiler.java | 12 +- .../decompile/vineflower/BaseSource.java | 34 +++++ .../decompile/vineflower/ClassSource.java | 50 +++++++ .../vineflower/DecompiledOutputSink.java | 50 +++++++ .../vineflower/DummyResultSaver.java | 53 +++++++ .../decompile/vineflower/LibrarySource.java | 30 ++++ .../vineflower/VineflowerConfig.java | 137 ++++++++++++++++++ .../vineflower/VineflowerDecompiler.java | 60 ++++++++ .../vineflower/VineflowerLogger.java | 29 ++++ .../decompile/DecompileManagerTest.java | 2 +- .../pane/editing/AbstractDecompilePane.java | 3 +- .../pane/editing/jvm/JvmDecompilerPane.java | 4 +- .../main/resources/translations/en_US.lang | 62 ++++++++ 30 files changed, 651 insertions(+), 82 deletions(-) create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java create mode 100644 recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java diff --git a/dependencies.gradle b/dependencies.gradle index 5aae413ec..26fc300a7 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -69,4 +69,6 @@ project.ext { richtextfx = 'org.fxmisc.richtext:richtextfx:0.11.2' treemapfx = 'software.coley:treemap-fx:1.1.0' + + vineflower = 'org.vineflower:vineflower:1.9.3' } \ No newline at end of file diff --git a/recaf-core/build.gradle b/recaf-core/build.gradle index 3964987eb..8b830d493 100644 --- a/recaf-core/build.gradle +++ b/recaf-core/build.gradle @@ -42,6 +42,7 @@ dependencies { api regex api jasm_core api jasm_composistion_jvm + api vineflower } // Force generation of gversion data class when the version information is not up-to-date diff --git a/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java b/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java index 4aa0c771e..58c01ad5a 100644 --- a/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java +++ b/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java @@ -15,6 +15,7 @@ public class BasicConfigContainer implements ConfigContainer { private final String group; private final String id; + /** * @param group * Container group. diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractDecompiler.java index 4860bb4a7..602c88a1d 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractDecompiler.java @@ -26,16 +26,19 @@ public AbstractDecompiler(@Nonnull String name, @Nonnull String version, @Nonnul this.config = config; } + @Nonnull @Override public String getName() { return name; } + @Nonnull @Override public String getVersion() { return version; } + @Nonnull @Override public DecompilerConfig getConfig() { return config; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java index b5341c4e2..10753f38e 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java @@ -1,6 +1,7 @@ package software.coley.recaf.services.decompile; import jakarta.annotation.Nonnull; +import org.objectweb.asm.ClassReader; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.info.properties.builtin.CachedDecompileProperty; import software.coley.recaf.workspace.model.Workspace; @@ -33,13 +34,14 @@ public void addJvmInputFilter(@Nonnull JvmInputFilter filter) { inputFilters.add(filter); } + @Nonnull @Override public final DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { // Check for cached result, returning the cached result if found // and only if the current config matches the one that yielded the cached result. DecompileResult cachedResult = CachedDecompileProperty.get(classInfo, this); if (cachedResult != null) { - if (cachedResult.getConfigHash() == getConfig().getConfigHash()) + if (cachedResult.getConfigHash() == getConfig().getHash()) return cachedResult; // Config changed, void the cache. @@ -50,15 +52,19 @@ public final DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull Jv byte[] bytecode = classInfo.getBytecode(); for (JvmInputFilter filter : inputFilters) bytecode = filter.filter(bytecode); + JvmClassInfo filteredBytecode = classInfo.toJvmClassBuilder().adaptFrom(new ClassReader(bytecode)).build(); // Pass to implementation. - DecompileResult result = decompile(workspace, classInfo.getName(), bytecode); + DecompileResult result = decompileInternal(workspace, filteredBytecode); // Cache result. CachedDecompileProperty.set(classInfo, this, result); return result; } + @Nonnull + protected abstract DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo); + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java new file mode 100644 index 000000000..58d16317b --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java @@ -0,0 +1,31 @@ +package software.coley.recaf.services.decompile; + +import jakarta.annotation.Nonnull; +import software.coley.recaf.config.BasicConfigContainer; + +/** + * Base class for fields needed by all decompiler configurations + * + * @author therathatter + */ +public class BaseDecompilerConfig extends BasicConfigContainer implements DecompilerConfig { + private int hash = 0; + + /** + * @param group Container group. + * @param id Container ID. + */ + public BaseDecompilerConfig(@Nonnull String group, @Nonnull String id) { + super(group, id); + } + + @Override + public int getHash() { + return hash; + } + + @Override + public void setHash(int hash) { + this.hash = hash; + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java index fb4c9d9f2..93bca7883 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java @@ -3,6 +3,7 @@ import jakarta.annotation.Nonnull; import jakarta.annotation.Nullable; import software.coley.recaf.info.properties.builtin.CachedDecompileProperty; +import software.coley.recaf.util.StringUtil; import java.util.Objects; @@ -20,19 +21,52 @@ public class DecompileResult { /** * @param text * Decompiled text. + * @param configHash + * Value of {@link DecompilerConfig#getHash()} of associated decompiler. + * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config. + */ + public DecompileResult(@Nonnull String text, int configHash) { + this.text = text; + this.type = ResultType.SUCCESS; + this.configHash = configHash; + this.exception = null; + } + + /** * @param exception * Failure reason. - * @param type - * Result type. * @param configHash - * Value of {@link DecompilerConfig#getConfigHash()} of associated decompiler. + * Value of {@link DecompilerConfig#getHash()} of associated decompiler. * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config. */ - public DecompileResult(@Nullable String text, @Nullable Throwable exception, @Nonnull ResultType type, int configHash) { - this.text = text; + public DecompileResult(@Nonnull Throwable exception, int configHash) { + this.text = "// " + StringUtil.traceToString(exception).replace("\n", "\n// "); + this.type = ResultType.FAILURE; + this.configHash = configHash; this.exception = exception; - this.type = type; + } + + /** + * @param configHash + * Value of {@link DecompilerConfig#getHash()} of associated decompiler. + * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config. + */ + public DecompileResult(int configHash) { + this.text = null; + this.type = ResultType.SKIPPED; this.configHash = configHash; + this.exception = null; + } + + /** + * @param text + * Decompiled text. + */ + public DecompileResult(@Nonnull String text) { + this.text = text; + this.type = ResultType.SKIPPED; + this.configHash = 0; + this.exception = null; } /** @@ -62,7 +96,7 @@ public ResultType getType() { } /** - * @return Value of {@link DecompilerConfig#getConfigHash()} of associated decompiler. + * @return Value of {@link DecompilerConfig#getHash()} of associated decompiler. * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config. */ public int getConfigHash() { diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/Decompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/Decompiler.java index fd3361edf..e8bfa0312 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/Decompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/Decompiler.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.decompile; +import jakarta.annotation.Nonnull; import software.coley.recaf.info.properties.builtin.CachedDecompileProperty; /** @@ -15,15 +16,18 @@ public interface Decompiler { /** * @return Decompiler name. */ + @Nonnull String getName(); /** * @return Decompiler version. */ + @Nonnull String getVersion(); /** * @return Decompiler config. */ + @Nonnull DecompilerConfig getConfig(); } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerConfig.java index 040c4c492..b68f05795 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerConfig.java @@ -10,7 +10,7 @@ *
* Tracks the hash of all contained {@link ConfigValue} so that when decompilers check for * {@link CachedDecompileProperty} they can see if the {@link DecompileResult#getConfigHash()} - * matches the current one of {@link #getConfigHash()}. + * matches the current one of {@link #getHash()}. * * @author Matt Coley */ @@ -24,20 +24,20 @@ public interface DecompilerConfig extends ConfigContainer { * * @return Unique hash of all contained {@link ConfigValue}. */ - int getConfigHash(); + int getHash(); /** * @param hash * New hash value. * - * @see #getConfigHash() For more detail. + * @see #getHash() For more detail. */ - void setConfigHash(int hash); + void setHash(int hash); /** * Called by implementations after they add all their values to the container. * - * @see #getConfigHash() For more detail. + * @see #getHash() For more detail. */ default void registerConfigValuesHashUpdates() { // Initial value computation. @@ -53,6 +53,6 @@ private void update() { .map(ConfigValue::getValue) .mapToInt(value -> value == null ? 0 : value.hashCode()) .reduce((a, b) -> (31 * a) + b) - .ifPresent(this::setConfigHash); + .ifPresent(this::setHash); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManager.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManager.java index 856d17686..f80210c62 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManager.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompilerManager.java @@ -97,7 +97,8 @@ public DecompilerManager(@Nonnull DecompilerManagerConfig config, * * @return Future of decompilation result. */ - public CompletableFuture decompile(Workspace workspace, JvmClassInfo classInfo) { + @Nonnull + public CompletableFuture decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { return decompile(getTargetJvmDecompiler(), workspace, classInfo); } @@ -113,7 +114,8 @@ public CompletableFuture decompile(Workspace workspace, JvmClas * * @return Future of decompilation result. */ - public CompletableFuture decompile(JvmDecompiler decompiler, Workspace workspace, JvmClassInfo classInfo) { + @Nonnull + public CompletableFuture decompile(@Nonnull JvmDecompiler decompiler, @Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { return CompletableFuture.supplyAsync(() -> decompiler.decompile(workspace, classInfo), decompileThreadPool); } @@ -127,7 +129,8 @@ public CompletableFuture decompile(JvmDecompiler decompiler, Wo * * @return Future of decompilation result. */ - public CompletableFuture decompile(Workspace workspace, AndroidClassInfo classInfo) { + @Nonnull + public CompletableFuture decompile(@Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) { return decompile(getTargetAndroidDecompiler(), workspace, classInfo); } @@ -143,13 +146,15 @@ public CompletableFuture decompile(Workspace workspace, Android * * @return Future of decompilation result. */ - public CompletableFuture decompile(AndroidDecompiler decompiler, Workspace workspace, AndroidClassInfo classInfo) { + @Nonnull + public CompletableFuture decompile(@Nonnull AndroidDecompiler decompiler, @Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) { return CompletableFuture.supplyAsync(() -> decompiler.decompile(workspace, classInfo), decompileThreadPool); } /** * @return Preferred JVM decompiler. */ + @Nonnull public JvmDecompiler getTargetJvmDecompiler() { return targetJvmDecompiler.getValue(); } @@ -157,6 +162,7 @@ public JvmDecompiler getTargetJvmDecompiler() { /** * @return Preferred Android decompiler. */ + @Nonnull public AndroidDecompiler getTargetAndroidDecompiler() { return targetAndroidDecompiler.getValue(); } @@ -165,7 +171,7 @@ public AndroidDecompiler getTargetAndroidDecompiler() { * @param decompiler * JVM decompiler to add. */ - public void register(JvmDecompiler decompiler) { + public void register(@Nonnull JvmDecompiler decompiler) { jvmDecompilers.put(decompiler.getName(), decompiler); } @@ -173,7 +179,7 @@ public void register(JvmDecompiler decompiler) { * @param decompiler * Android decompiler to add. */ - public void register(AndroidDecompiler decompiler) { + public void register(@Nonnull AndroidDecompiler decompiler) { androidDecompilers.put(decompiler.getName(), decompiler); } @@ -184,7 +190,7 @@ public void register(AndroidDecompiler decompiler) { * @return Decompiler instance, or {@code null} if nothing by the ID was found. */ @Nullable - public JvmDecompiler getJvmDecompiler(String name) { + public JvmDecompiler getJvmDecompiler(@Nonnull String name) { return jvmDecompilers.get(name); } @@ -195,13 +201,14 @@ public JvmDecompiler getJvmDecompiler(String name) { * @return Decompiler instance, or {@code null} if nothing by the ID was found. */ @Nullable - public AndroidDecompiler getAndroidDecompiler(String name) { + public AndroidDecompiler getAndroidDecompiler(@Nonnull String name) { return androidDecompilers.get(name); } /** * @return Available JVM class decompilers. */ + @Nonnull public Collection getJvmDecompilers() { return jvmDecompilers.values(); } @@ -209,6 +216,7 @@ public Collection getJvmDecompilers() { /** * @return Available android class decompilers. */ + @Nonnull public Collection getAndroidDecompilers() { return androidDecompilers.values(); } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/JvmDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/JvmDecompiler.java index 4f1ad97e4..baf8a003f 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/JvmDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/JvmDecompiler.java @@ -24,17 +24,6 @@ public interface JvmDecompiler extends Decompiler { * * @return Decompilation result. */ + @Nonnull DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo); - - /** - * @param workspace - * Workspace to pull data from. - * @param name - * Class name. - * @param bytecode - * Class bytecode. - * - * @return Decompilation result. - */ - DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String name, @Nonnull byte[] bytecode); } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopAndroidDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopAndroidDecompiler.java index 8c4821139..31c3c42d0 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopAndroidDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopAndroidDecompiler.java @@ -25,6 +25,6 @@ public static NoopAndroidDecompiler getInstance() { @Override public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull AndroidClassInfo classInfo) { - return new DecompileResult(null, null, DecompileResult.ResultType.SKIPPED, getConfig().getConfigHash()); + return new DecompileResult(getConfig().getHash()); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopDecompilerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopDecompilerConfig.java index 0a92aae69..520bdae75 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopDecompilerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopDecompilerConfig.java @@ -17,12 +17,12 @@ public NoopDecompilerConfig() { } @Override - public int getConfigHash() { + public int getHash() { return 0; } @Override - public void setConfigHash(int hash) { + public void setHash(int hash) { // no-op } } \ No newline at end of file diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopJvmDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopJvmDecompiler.java index 341009830..f4cf51e8d 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopJvmDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/NoopJvmDecompiler.java @@ -1,6 +1,7 @@ package software.coley.recaf.services.decompile; import jakarta.annotation.Nonnull; +import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.workspace.model.Workspace; /** @@ -22,8 +23,9 @@ public static NoopJvmDecompiler getInstance() { return INSTANCE; } + @Nonnull @Override - public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String name, @Nonnull byte[] bytecode) { - return new DecompileResult(null, null, DecompileResult.ResultType.SKIPPED, getConfig().getConfigHash()); + protected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { + return new DecompileResult(getConfig().getHash()); } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java index 719a784f9..ab0e2f581 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java @@ -10,10 +10,9 @@ import org.benf.cfr.reader.util.getopt.PermittedOptionProvider; import software.coley.observables.ObservableInteger; import software.coley.observables.ObservableObject; -import software.coley.recaf.config.BasicConfigContainer; import software.coley.recaf.config.BasicConfigValue; import software.coley.recaf.config.ConfigGroups; -import software.coley.recaf.services.decompile.DecompilerConfig; +import software.coley.recaf.services.decompile.BaseDecompilerConfig; import software.coley.recaf.util.ReflectUtil; import software.coley.recaf.util.StringUtil; @@ -29,7 +28,7 @@ */ @ApplicationScoped @SuppressWarnings("all") // ignore unused refs / typos -public class CfrConfig extends BasicConfigContainer implements DecompilerConfig { +public class CfrConfig extends BaseDecompilerConfig { private final ObservableObject stringbuffer = new ObservableObject<>(BooleanOption.DEFAULT); private final ObservableObject stringbuilder = new ObservableObject<>(BooleanOption.DEFAULT); private final ObservableObject stringconcat = new ObservableObject<>(BooleanOption.DEFAULT); @@ -110,7 +109,6 @@ public class CfrConfig extends BasicConfigContainer implements DecompilerConfig private final ObservableObject allowmalformedswitch = new ObservableObject<>(TrooleanOption.DEFAULT); private final ObservableObject elidescala = new ObservableObject<>(BooleanOption.DEFAULT); private final ObservableObject usesignatures = new ObservableObject<>(BooleanOption.DEFAULT); - private int hash = 0; @Inject public CfrConfig() { @@ -687,16 +685,6 @@ public ObservableObject getUsesignatures() { return usesignatures; } - @Override - public int getConfigHash() { - return hash; - } - - @Override - public void setConfigHash(int hash) { - this.hash = hash; - } - /** * Wrapper for CFR boolean values. */ diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrDecompiler.java index e24632385..35224bde4 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrDecompiler.java @@ -6,6 +6,7 @@ import org.benf.cfr.reader.api.CfrDriver; import org.benf.cfr.reader.util.CfrVersionInfo; import org.benf.cfr.reader.util.DecompilerComment; +import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.services.decompile.AbstractJvmDecompiler; import software.coley.recaf.services.decompile.DecompileResult; import software.coley.recaf.util.ReflectUtil; @@ -36,8 +37,11 @@ public CfrDecompiler(@Nonnull CfrConfig config) { this.config = config; } + @Nonnull @Override - public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String name, @Nonnull byte[] bytecode) { + protected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { + String name = classInfo.getName(); + byte[] bytecode = classInfo.getBytecode(); ClassSource source = new ClassSource(workspace, name, bytecode); SinkFactoryImpl sink = new SinkFactoryImpl(); CfrDriver driver = new CfrDriver.Builder() @@ -47,12 +51,13 @@ public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String n .build(); driver.analyse(Collections.singletonList(name)); String decompile = sink.getDecompilation(); - int configHash = getConfig().getConfigHash(); + int configHash = getConfig().getHash(); if (decompile == null) - return new DecompileResult(null, sink.getException(), DecompileResult.ResultType.FAILURE, configHash); - return new DecompileResult(filter(decompile), null, DecompileResult.ResultType.SUCCESS, configHash); + return new DecompileResult(sink.getException(), configHash); + return new DecompileResult(filter(decompile), configHash); } + @Nonnull @Override public CfrConfig getConfig() { return (CfrConfig) super.getConfig(); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java index 855b079e7..892ee2e05 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java @@ -9,10 +9,9 @@ import software.coley.observables.ObservableBoolean; import software.coley.observables.ObservableInteger; import software.coley.observables.ObservableObject; -import software.coley.recaf.config.BasicConfigContainer; import software.coley.recaf.config.BasicConfigValue; import software.coley.recaf.config.ConfigGroups; -import software.coley.recaf.services.decompile.DecompilerConfig; +import software.coley.recaf.services.decompile.BaseDecompilerConfig; /** * Config for {@link ProcyonDecompiler} @@ -20,7 +19,7 @@ * @author Matt Coley */ @ApplicationScoped -public class ProcyonConfig extends BasicConfigContainer implements DecompilerConfig { +public class ProcyonConfig extends BaseDecompilerConfig { private final ObservableBoolean includeLineNumbersInBytecode = new ObservableBoolean(true); private final ObservableBoolean showSyntheticMembers = new ObservableBoolean(false); private final ObservableBoolean alwaysGenerateExceptionVariableForCatchBlocks = new ObservableBoolean(true); @@ -41,8 +40,6 @@ public class ProcyonConfig extends BasicConfigContainer implements DecompilerCon private final ObservableInteger textBlockLineMinimum = new ObservableInteger(3); private final ObservableObject forcedCompilerTarget = new ObservableObject<>(null); private final ObservableObject bytecodeOutputOptions = new ObservableObject<>(BytecodeOutputOptions.createDefault()); - private int hash; - @Inject public ProcyonConfig() { super(ConfigGroups.SERVICE_DECOMPILE, "decompiler-procyon" + CONFIG_SUFFIX); @@ -196,14 +193,4 @@ public ObservableObject getForcedCompilerTarget() { public ObservableObject getBytecodeOutputOptions() { return bytecodeOutputOptions; } - - @Override - public int getConfigHash() { - return hash; - } - - @Override - public void setConfigHash(int hash) { - this.hash = hash; - } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonDecompiler.java index 5e3fe52ec..179c34eed 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonDecompiler.java @@ -8,6 +8,7 @@ import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.services.decompile.AbstractJvmDecompiler; import software.coley.recaf.services.decompile.DecompileResult; import software.coley.recaf.workspace.model.Workspace; @@ -36,8 +37,11 @@ public ProcyonDecompiler(@Nonnull ProcyonConfig config) { this.config = config; } + @Nonnull @Override - public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String name, @Nonnull byte[] bytecode) { + protected DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo) { + String name = classInfo.getName(); + byte[] bytecode = classInfo.getBytecode(); ITypeLoader loader = new CompositeTypeLoader( new TargetedTypeLoader(name, bytecode), new WorkspaceTypeLoader(workspace) @@ -51,10 +55,10 @@ public DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull String n StringWriter writer = new StringWriter(); settings.getLanguage().decompileType(ref.resolve(), new PlainTextOutput(writer), decompilationOptions); String decompile = writer.toString(); - int configHash = getConfig().getConfigHash(); + int configHash = getConfig().getHash(); if (decompile == null) - return new DecompileResult(null, new IllegalStateException("Missing decompilation output"), DecompileResult.ResultType.FAILURE, configHash); - return new DecompileResult(decompile, null, DecompileResult.ResultType.SUCCESS, configHash); + return new DecompileResult(new IllegalStateException("Missing decompilation output"), configHash); + return new DecompileResult(decompile, configHash); } /** diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java new file mode 100644 index 000000000..a8b061317 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java @@ -0,0 +1,34 @@ +package software.coley.recaf.services.decompile.vineflower; + +import org.jetbrains.java.decompiler.main.extern.IContextSource; +import software.coley.recaf.path.ClassPathNode; +import software.coley.recaf.workspace.model.Workspace; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +/** + * Base Vineflower class/library source + * + * @author therathatter + */ +public abstract class BaseSource implements IContextSource { + protected final Workspace workspace; + + protected BaseSource(Workspace workspace) { + this.workspace = workspace; + } + + @Override + public String getName() { + return "Recaf"; + } + + @Override + public InputStream getInputStream(String resource) { + String name = resource.substring(0, resource.length() - IContextSource.CLASS_SUFFIX.length()); + ClassPathNode node = workspace.findClass(name); + if (node == null) return InputStream.nullInputStream(); + return new ByteArrayInputStream(node.getValue().asJvmClass().getBytecode()); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java new file mode 100644 index 000000000..92735fdda --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java @@ -0,0 +1,50 @@ +package software.coley.recaf.services.decompile.vineflower; + +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import software.coley.recaf.info.InnerClassInfo; +import software.coley.recaf.info.JvmClassInfo; +import software.coley.recaf.workspace.model.Workspace; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Single class source for Vineflower + * + * @author Matt Coley + * @author therathatter + */ +public class ClassSource extends BaseSource { + private final JvmClassInfo info; + private final DecompiledOutputSink sink; + + protected ClassSource(Workspace workspace, JvmClassInfo info) { + super(workspace); + this.info = info; + sink = new DecompiledOutputSink(info); + } + + @Override + public Entries getEntries() { + // TODO: Bug in QF/VF makes it so that 'addLibrary' doesn't yield inner info for a class provided with 'addSource' + // So for now until this is fixed upstream we will also supply inners here. + // This will make QF/VF decompile each inner class separately as well, but its the best fix for now without + // too much of a perf hit. + List entries = new ArrayList<>(); + + entries.add(new Entry(info.getName(), Entry.BASE_VERSION)); + for (InnerClassInfo innerClass : info.getInnerClasses()) + entries.add(new Entry(innerClass.getName(), Entry.BASE_VERSION)); + return new Entries(entries, Collections.emptyList(), Collections.emptyList()); + } + + DecompiledOutputSink getSink() { + return this.sink; + } + + @Override + public IOutputSink createOutputSink(IResultSaver saver) { + return sink; + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java new file mode 100644 index 000000000..f21d3327e --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java @@ -0,0 +1,50 @@ +package software.coley.recaf.services.decompile.vineflower; + +import org.jetbrains.java.decompiler.main.extern.IContextSource; +import software.coley.recaf.info.JvmClassInfo; + +import java.io.IOException; + +/** + * Output sink for Vineflower decompiler + * + * @author therathatter + */ +public class DecompiledOutputSink implements IContextSource.IOutputSink { + protected final JvmClassInfo target; + protected final ThreadLocal out = new ThreadLocal<>(); + + DecompiledOutputSink(JvmClassInfo target) { + this.target = target; + } + + ThreadLocal getDecompiledOutput() { + return out; + } + + @Override + public void begin() { + + } + + @Override + public void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) { + if (target.getName().equals(qualifiedName)) + out.set(content); + } + + @Override + public void acceptDirectory(String directory) { + + } + + @Override + public void acceptOther(String path) { + + } + + @Override + public void close() throws IOException { + + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java new file mode 100644 index 000000000..10c25ac00 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java @@ -0,0 +1,53 @@ +package software.coley.recaf.services.decompile.vineflower; + +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.util.jar.Manifest; + +/** + * Dummy result saver to prevent Vineflower from trying to touch disk. + * + * @author therathatter + */ +public class DummyResultSaver implements IResultSaver { + + @Override + public void saveFolder(String s) { + + } + + @Override + public void copyFile(String s, String s1, String s2) { + + } + + @Override + public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) { + + } + + @Override + public void createArchive(String s, String s1, Manifest manifest) { + + } + + @Override + public void saveDirEntry(String s, String s1, String s2) { + + } + + @Override + public void copyEntry(String s, String s1, String s2, String s3) { + + } + + @Override + public void saveClassEntry(String s, String s1, String s2, String s3, String s4) { + + } + + @Override + public void closeArchive(String s, String s1) { + + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java new file mode 100644 index 000000000..aa3b63650 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java @@ -0,0 +1,30 @@ +package software.coley.recaf.services.decompile.vineflower; + +import software.coley.recaf.workspace.model.Workspace; +import software.coley.recaf.workspace.model.resource.WorkspaceResource; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Full library source for Vineflower + * + * @author Matt Coley + * @author therathatter + */ +public class LibrarySource extends BaseSource { + protected LibrarySource(Workspace workspace) { + super(workspace); + } + + @Override + public Entries getEntries() { + List entries = workspace.getAllResources(false).stream() + .map(WorkspaceResource::getJvmClassBundle) + .flatMap(c -> c.keySet().stream()) + .map(className -> new Entry(className, Entry.BASE_VERSION)) + .collect(Collectors.toList()); + return new Entries(entries, Collections.emptyList(), Collections.emptyList()); + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java new file mode 100644 index 000000000..e322d4643 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java @@ -0,0 +1,137 @@ +package software.coley.recaf.services.decompile.vineflower; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import software.coley.observables.ObservableBoolean; +import software.coley.recaf.config.BasicConfigValue; +import software.coley.recaf.config.ConfigGroups; +import software.coley.recaf.services.decompile.BaseDecompilerConfig; + +import java.util.HashMap; +import java.util.Map; + +/** + * Config for {@link VineflowerDecompiler} + * + * @author therathatter + */ +@ApplicationScoped +public class VineflowerConfig extends BaseDecompilerConfig { + private final ObservableBoolean REMOVE_BRIDGE = new ObservableBoolean(true); + private final ObservableBoolean REMOVE_SYNTHETIC = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILE_INNER = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILE_CLASS_1_4 = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILE_ASSERTIONS = new ObservableBoolean(true); + private final ObservableBoolean HIDE_EMPTY_SUPER = new ObservableBoolean(true); + private final ObservableBoolean HIDE_DEFAULT_CONSTRUCTOR = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILE_GENERIC_SIGNATURES = new ObservableBoolean(true); + private final ObservableBoolean NO_EXCEPTIONS_RETURN = new ObservableBoolean(true); + private final ObservableBoolean ENSURE_SYNCHRONIZED_MONITOR = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILE_ENUM = new ObservableBoolean(true); + private final ObservableBoolean REMOVE_GET_CLASS_NEW = new ObservableBoolean(true); + private final ObservableBoolean LITERALS_AS_IS = new ObservableBoolean(false); + private final ObservableBoolean BOOLEAN_TRUE_ONE = new ObservableBoolean(true); + private final ObservableBoolean ASCII_STRING_CHARACTERS = new ObservableBoolean(false); + private final ObservableBoolean SYNTHETIC_NOT_SET = new ObservableBoolean(false); + private final ObservableBoolean UNDEFINED_PARAM_TYPE_OBJECT = new ObservableBoolean(true); + private final ObservableBoolean USE_DEBUG_VAR_NAMES = new ObservableBoolean(true); + private final ObservableBoolean USE_METHOD_PARAMETERS = new ObservableBoolean(true); + private final ObservableBoolean REMOVE_EMPTY_RANGES = new ObservableBoolean(true); + private final ObservableBoolean FINALLY_DEINLINE = new ObservableBoolean(true); + private final ObservableBoolean IDEA_NOT_NULL_ANNOTATION = new ObservableBoolean(true); + private final ObservableBoolean LAMBDA_TO_ANONYMOUS_CLASS = new ObservableBoolean(false); + private final ObservableBoolean BYTECODE_SOURCE_MAPPING = new ObservableBoolean(false); + private final ObservableBoolean DUMP_CODE_LINES = new ObservableBoolean(false); + private final ObservableBoolean IGNORE_INVALID_BYTECODE = new ObservableBoolean(false); + private final ObservableBoolean VERIFY_ANONYMOUS_CLASSES = new ObservableBoolean(false); + private final ObservableBoolean TERNARY_CONSTANT_SIMPLIFICATION = new ObservableBoolean(false); + private final ObservableBoolean OVERRIDE_ANNOTATION = new ObservableBoolean(true); + private final ObservableBoolean PATTERN_MATCHING = new ObservableBoolean(true); + private final ObservableBoolean TRY_LOOP_FIX = new ObservableBoolean(true); + private final ObservableBoolean TERNARY_CONDITIONS= new ObservableBoolean(true); + private final ObservableBoolean SWITCH_EXPRESSIONS = new ObservableBoolean(true); + private final ObservableBoolean SHOW_HIDDEN_STATEMENTS = new ObservableBoolean(false); + private final ObservableBoolean SIMPLIFY_STACK_SECOND_PASS = new ObservableBoolean(true); + private final ObservableBoolean VERIFY_VARIABLE_MERGES = new ObservableBoolean(false); + private final ObservableBoolean DECOMPILE_PREVIEW = new ObservableBoolean(true); + private final ObservableBoolean EXPLICIT_GENERIC_ARGUMENTS = new ObservableBoolean(false); + private final ObservableBoolean INLINE_SIMPLE_LAMBDAS = new ObservableBoolean(true); + private final ObservableBoolean USE_JAD_VARNAMING = new ObservableBoolean(false); + private final ObservableBoolean USE_JAD_PARAMETER_NAMING = new ObservableBoolean(false); + private final ObservableBoolean SKIP_EXTRA_FILES = new ObservableBoolean(false); + private final ObservableBoolean WARN_INCONSISTENT_INNER_CLASSES = new ObservableBoolean(true); + private final ObservableBoolean DUMP_BYTECODE_ON_ERROR = new ObservableBoolean(true); + private final ObservableBoolean DUMP_EXCEPTION_ON_ERROR = new ObservableBoolean(true); + private final ObservableBoolean DECOMPILER_COMMENTS = new ObservableBoolean(true); + private final ObservableBoolean SOURCE_FILE_COMMENTS = new ObservableBoolean(false); + private final ObservableBoolean DECOMPILE_COMPLEX_CONDYS = new ObservableBoolean(false); + private final ObservableBoolean FORCE_JSR_INLINE = new ObservableBoolean(false); + + @Inject + public VineflowerConfig() { + super(ConfigGroups.SERVICE_DECOMPILE, "decompiler-vineflower" + CONFIG_SUFFIX); + addValue(new BasicConfigValue<>( "rbr", boolean.class, REMOVE_BRIDGE)); + addValue(new BasicConfigValue<>( "rsy", boolean.class, REMOVE_SYNTHETIC)); + addValue(new BasicConfigValue<>( "din", boolean.class, DECOMPILE_INNER)); + addValue(new BasicConfigValue<>( "dc4", boolean.class, DECOMPILE_CLASS_1_4)); + addValue(new BasicConfigValue<>( "das", boolean.class, DECOMPILE_ASSERTIONS)); + addValue(new BasicConfigValue<>( "hes", boolean.class, HIDE_EMPTY_SUPER)); + addValue(new BasicConfigValue<>( "hdc", boolean.class, HIDE_DEFAULT_CONSTRUCTOR)); + addValue(new BasicConfigValue<>( "dgs", boolean.class, DECOMPILE_GENERIC_SIGNATURES)); + addValue(new BasicConfigValue<>( "ner", boolean.class, NO_EXCEPTIONS_RETURN)); + addValue(new BasicConfigValue<>( "esm", boolean.class, ENSURE_SYNCHRONIZED_MONITOR)); + addValue(new BasicConfigValue<>( "den", boolean.class, DECOMPILE_ENUM)); + addValue(new BasicConfigValue<>( "dpr", boolean.class, DECOMPILE_PREVIEW)); + addValue(new BasicConfigValue<>( "rgn", boolean.class, REMOVE_GET_CLASS_NEW)); + addValue(new BasicConfigValue<>( "lit", boolean.class, LITERALS_AS_IS)); + addValue(new BasicConfigValue<>( "bto", boolean.class, BOOLEAN_TRUE_ONE)); + addValue(new BasicConfigValue<>( "asc", boolean.class, ASCII_STRING_CHARACTERS)); + addValue(new BasicConfigValue<>( "nns", boolean.class, SYNTHETIC_NOT_SET)); + addValue(new BasicConfigValue<>( "uto", boolean.class, UNDEFINED_PARAM_TYPE_OBJECT)); + addValue(new BasicConfigValue<>( "udv", boolean.class, USE_DEBUG_VAR_NAMES)); + addValue(new BasicConfigValue<>( "ump", boolean.class, USE_METHOD_PARAMETERS)); + addValue(new BasicConfigValue<>( "rer", boolean.class, REMOVE_EMPTY_RANGES)); + addValue(new BasicConfigValue<>( "fdi", boolean.class, FINALLY_DEINLINE)); + addValue(new BasicConfigValue<>( "inn", boolean.class, IDEA_NOT_NULL_ANNOTATION)); + addValue(new BasicConfigValue<>( "lac", boolean.class, LAMBDA_TO_ANONYMOUS_CLASS)); + addValue(new BasicConfigValue<>( "bsm", boolean.class, BYTECODE_SOURCE_MAPPING)); + addValue(new BasicConfigValue<>( "dcl", boolean.class, DUMP_CODE_LINES)); + addValue(new BasicConfigValue<>( "iib", boolean.class, IGNORE_INVALID_BYTECODE)); + addValue(new BasicConfigValue<>( "vac", boolean.class, VERIFY_ANONYMOUS_CLASSES)); + addValue(new BasicConfigValue<>( "tcs", boolean.class, TERNARY_CONSTANT_SIMPLIFICATION)); + addValue(new BasicConfigValue<>( "pam", boolean.class, PATTERN_MATCHING)); + addValue(new BasicConfigValue<>( "tlf", boolean.class, TRY_LOOP_FIX)); + addValue(new BasicConfigValue<>( "tco", boolean.class, TERNARY_CONDITIONS)); + addValue(new BasicConfigValue<>( "swe", boolean.class, SWITCH_EXPRESSIONS)); + addValue(new BasicConfigValue<>( "shs", boolean.class, SHOW_HIDDEN_STATEMENTS)); + addValue(new BasicConfigValue<>( "ovr", boolean.class, OVERRIDE_ANNOTATION)); + addValue(new BasicConfigValue<>( "ssp", boolean.class, SIMPLIFY_STACK_SECOND_PASS)); + addValue(new BasicConfigValue<>( "vvm", boolean.class, VERIFY_VARIABLE_MERGES)); + addValue(new BasicConfigValue<>( "ega", boolean.class, EXPLICIT_GENERIC_ARGUMENTS)); + addValue(new BasicConfigValue<>( "isl", boolean.class, INLINE_SIMPLE_LAMBDAS)); + addValue(new BasicConfigValue<>( "jvn", boolean.class, USE_JAD_VARNAMING)); + addValue(new BasicConfigValue<>( "jpr", boolean.class, USE_JAD_PARAMETER_NAMING)); + addValue(new BasicConfigValue<>( "sef", boolean.class, SKIP_EXTRA_FILES)); + addValue(new BasicConfigValue<>( "win", boolean.class, WARN_INCONSISTENT_INNER_CLASSES)); + addValue(new BasicConfigValue<>( "dbe", boolean.class, DUMP_BYTECODE_ON_ERROR)); + addValue(new BasicConfigValue<>( "dee", boolean.class, DUMP_EXCEPTION_ON_ERROR)); + addValue(new BasicConfigValue<>( "dec", boolean.class, DECOMPILER_COMMENTS)); + addValue(new BasicConfigValue<>( "sfc", boolean.class, SOURCE_FILE_COMMENTS)); + addValue(new BasicConfigValue<>( "dcc", boolean.class, DECOMPILE_COMPLEX_CONDYS)); + addValue(new BasicConfigValue<>( "fji", boolean.class, FORCE_JSR_INLINE)); + registerConfigValuesHashUpdates(); + } + + Map getFernflowerProperties() { + Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); + + getValues().forEach((key, value) -> { + if (value.getValue() instanceof Boolean) { + properties.put(key, (Boolean)value.getValue() ? "1" : "0"); + } + }); + + return properties; + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java new file mode 100644 index 000000000..c1a9c8f40 --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java @@ -0,0 +1,60 @@ +package software.coley.recaf.services.decompile.vineflower; + +import jakarta.annotation.Nonnull; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import software.coley.recaf.info.JvmClassInfo; +import software.coley.recaf.services.decompile.AbstractJvmDecompiler; +import software.coley.recaf.services.decompile.DecompileResult; +import software.coley.recaf.workspace.model.Workspace; + +/** + * Vineflower decompiler implementation. + * + * @author therathatter + */ +@ApplicationScoped +public class VineflowerDecompiler extends AbstractJvmDecompiler { + public static final String NAME = "Vineflower"; + private final VineflowerConfig config; + private final IFernflowerLogger logger = new VineflowerLogger(); + private final IResultSaver dummySaver = new DummyResultSaver(); + + /** + * New Vineflower decompiler instance. + * + * @param config Decompiler configuration. + */ + @Inject + public VineflowerDecompiler(@Nonnull VineflowerConfig config) { + // Change this version to be dynamic when / if the Vineflower authors make a function that returns the version... + super(NAME, "1.9.3", config); + this.config = config; + } + + @Nonnull + @Override + public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { + Fernflower fernflower = new Fernflower(dummySaver, config.getFernflowerProperties(), logger); + + try { + ClassSource source = new ClassSource(workspace, info); + fernflower.addSource(source); + fernflower.addLibrary(new LibrarySource(workspace)); + fernflower.decompileContext(); + + String decompiled = source.getSink().getDecompiledOutput().get(); + + if (decompiled == null || decompiled.isEmpty()) { + return new DecompileResult(new IllegalStateException("Missing decompilation output"), config.getHash()); + } + + return new DecompileResult(decompiled, config.getHash()); + } catch (Exception e) { + return new DecompileResult(e, config.getHash()); + } + } +} diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java new file mode 100644 index 000000000..f2eb0854e --- /dev/null +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java @@ -0,0 +1,29 @@ +package software.coley.recaf.services.decompile.vineflower; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.slf4j.Logger; +import software.coley.recaf.analytics.logging.Logging; + +/** + * Logger for Vineflower + * + * @author therathatter + */ +public class VineflowerLogger extends IFernflowerLogger { + private static final Logger logger = Logging.get(VineflowerLogger.class); + + @Override + public void writeMessage(String s, Severity severity) { + switch (severity) { + case TRACE -> logger.trace(s); + case INFO -> logger.info(s); + case WARN -> logger.warn(s); + case ERROR -> logger.error(s); + } + } + + @Override + public void writeMessage(String s, Severity severity, Throwable throwable) { + writeMessage(s, severity); + } +} diff --git a/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java b/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java index f6e4b1137..1c4496e37 100644 --- a/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java +++ b/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java @@ -67,7 +67,7 @@ private static void runJvmDecompilation(JvmDecompiler decompiler) { assertSame(firstResult, newResult, "Decompiler did not cache results"); // Change the decompiler hash. The decompiler result should change. - decompiler.getConfig().setConfigHash(-1); + decompiler.getConfig().setHash(-1); newResult = decompilerManager.decompile(decompiler, workspace, classToDecompile) .get(1, TimeUnit.SECONDS); assertNotSame(firstResult, newResult, "Decompiler used cached result even though config hash changed"); diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilePane.java b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilePane.java index 98621351b..01241828f 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilePane.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/AbstractDecompilePane.java @@ -22,7 +22,6 @@ import org.slf4j.Logger; import software.coley.observables.ObservableBoolean; import software.coley.observables.ObservableObject; -import software.coley.recaf.Bootstrap; import software.coley.recaf.analytics.logging.Logging; import software.coley.recaf.info.AndroidClassInfo; import software.coley.recaf.info.ClassInfo; @@ -317,7 +316,7 @@ private DecompileResult timeoutResult() { info.getBytecode().length, jvmDecompiler.getName(), jvmDecompiler.getVersion(), config.getTimeoutSeconds().getValue() - ), null, DecompileResult.ResultType.SKIPPED, 0); + )); } /** diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java index 5f8b53375..edd968413 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/editing/jvm/JvmDecompilerPane.java @@ -246,9 +246,9 @@ private void save() { // Update cached decompile property to current editor text. // If the class is opened later, we can use the code we compiled with. JvmDecompiler currentDecompiler = decompiler.getValue(); - int configHash = currentDecompiler.getConfig().getConfigHash(); + int configHash = currentDecompiler.getConfig().getHash(); CachedDecompileProperty.set(newInfo, currentDecompiler, - new DecompileResult(editor.getText(), null, DecompileResult.ResultType.SUCCESS, configHash)); + new DecompileResult(editor.getText(), configHash)); // Update the class in the bundle. bundle.put(newInfo); diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index 5e65cf33a..6fd19fee6 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -553,6 +553,68 @@ service.decompile.decompiler-procyon-config.showDebugLineNumbers=Show debug line service.decompile.decompiler-procyon-config.showSyntheticMembers=Show synthetic members service.decompile.decompiler-procyon-config.simplifyMemberReferences=Simplify member references service.decompile.decompiler-procyon-config.textBlockLineMinimum=Text block minimum lines +service.decompile.decompiler-vineflower-config=Vineflower +service.decompile.decompiler-vineflower-config.rbr=Remove bridge methods +service.decompile.decompiler-vineflower-config.rsy=Remove synthetic methods and fields +service.decompile.decompiler-vineflower-config.din=Decompile inner classes +service.decompile.decompiler-vineflower-config.dc4=Decompile Java 4 class references +service.decompile.decompiler-vineflower-config.das=Decompile assertions +service.decompile.decompiler-vineflower-config.hes=Hide empty super() +service.decompile.decompiler-vineflower-config.hdc=Hide default constructor +service.decompile.decompiler-vineflower-config.dgs=Decompile generics +service.decompile.decompiler-vineflower-config.ner=No exceptions in return +service.decompile.decompiler-vineflower-config.esm=Ensure synchronized ranges are complete +service.decompile.decompiler-vineflower-config.den=Decompile enums +service.decompile.decompiler-vineflower-config.dpr=Decompile preview features +service.decompile.decompiler-vineflower-config.rgn=Remove reference getClass() +service.decompile.decompiler-vineflower-config.lit=Keep literals as is +service.decompile.decompiler-vineflower-config.bto=Represent boolean as 0/1 +service.decompile.decompiler-vineflower-config.asc=ASCII string characters +service.decompile.decompiler-vineflower-config.nns=Synthetic not set +service.decompile.decompiler-vineflower-config.uto=Treat undefined param type as object +service.decompile.decompiler-vineflower-config.udv=Use LVT names +service.decompile.decompiler-vineflower-config.ump=Use method parameters +service.decompile.decompiler-vineflower-config.rer=Remove empty try-catch blocks +service.decompile.decompiler-vineflower-config.fdi=Decompile finally blocks +service.decompile.decompiler-vineflower-config.inn=Resugar IntelliJ IDEA @NotNull +service.decompile.decompiler-vineflower-config.lac=Decompile lambdas as anonymous classes +service.decompile.decompiler-vineflower-config.bsm=Bytecode to source mapping +service.decompile.decompiler-vineflower-config.dcl=Dump code lines +service.decompile.decompiler-vineflower-config.iib=Ignore invalid bytecode +service.decompile.decompiler-vineflower-config.vac=Verify anonymous classes +service.decompile.decompiler-vineflower-config.tcs=Ternary constant simplification +service.decompile.decompiler-vineflower-config.pam=Pattern matching +service.decompile.decompiler-vineflower-config.tlf=Try-loop fix +service.decompile.decompiler-vineflower-config.tco=Ternary in if conditions (experimental) +service.decompile.decompiler-vineflower-config.swe=Decompile switch expressions +service.decompile.decompiler-vineflower-config.shs=Show hidden statements (debug) +service.decompile.decompiler-vineflower-config.ovr=Override annotation +service.decompile.decompiler-vineflower-config.ssp=Second pass stack simplification +service.decompile.decompiler-vineflower-config.vvm=Verify variable merges (experimental) +service.decompile.decompiler-vineflower-config.iec=Include entire classpath +service.decompile.decompiler-vineflower-config.jrt=Include Java runtime +service.decompile.decompiler-vineflower-config.ega=Explicit generic arguments +service.decompile.decompiler-vineflower-config.isl=Inline simple lambdas +service.decompile.decompiler-vineflower-config.log=Logging level +service.decompile.decompiler-vineflower-config.mpm=Max processing method +service.decompile.decompiler-vineflower-config.ren=Rename entities / members +service.decompile.decompiler-vineflower-config.urc=User renamer class +service.decompile.decompiler-vineflower-config.nls=New line separator +service.decompile.decompiler-vineflower-config.ind=Indent string +service.decompile.decompiler-vineflower-config.pll=Preferred line length +service.decompile.decompiler-vineflower-config.ban=User renamer class +service.decompile.decompiler-vineflower-config.erm=Error message +service.decompile.decompiler-vineflower-config.thr=Thread count +service.decompile.decompiler-vineflower-config.jvn=Use JAD style variable naming +service.decompile.decompiler-vineflower-config.jpr=Use JAD style parameter naming +service.decompile.decompiler-vineflower-config.sef=Skip extra files +service.decompile.decompiler-vineflower-config.win=Warn about inconsistent inner attributes +service.decompile.decompiler-vineflower-config.dbe=Dump bytecode on error +service.decompile.decompiler-vineflower-config.dee=Dump exceptions on error +service.decompile.decompiler-vineflower-config.dec=Decompiler comments +service.decompile.decompiler-vineflower-config.sfc=Source file comments +service.decompile.decompiler-vineflower-config.dcc=Decompile complex constant-dynamic expressions +service.decompile.decompiler-vineflower-config.fji=Force JSR inline service.decompile.decompilers-config=Decompile manager service.decompile.decompilers-config.pref-android-decompiler=Preferred Android decompiler service.decompile.decompilers-config.pref-jvm-decompiler=Preferred Java decompiler From 088c27c55e4062cbd11333455505abd3663c4e2c Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 14 Jan 2024 05:43:06 -0500 Subject: [PATCH 2/4] Additional documentation of new Vineflower component, config UI tweaks --- .../recaf/config/BasicConfigContainer.java | 1 - .../coley/recaf/config/ConfigGroups.java | 6 +- .../decompile/AbstractJvmDecompiler.java | 10 + .../decompile/BaseDecompilerConfig.java | 34 +- .../services/decompile/cfr/CfrConfig.java | 2 +- .../decompile/procyon/ProcyonConfig.java | 2 +- .../decompile/vineflower/BaseSource.java | 37 +- .../decompile/vineflower/ClassSource.java | 74 +-- .../vineflower/DecompiledOutputSink.java | 83 +-- .../vineflower/DummyResultSaver.java | 17 +- .../decompile/vineflower/LibrarySource.java | 31 +- .../vineflower/VineflowerConfig.java | 481 +++++++++++++----- .../vineflower/VineflowerDecompiler.java | 79 +-- .../vineflower/VineflowerLogger.java | 28 +- .../services/config/ConfigIconManager.java | 1 + .../coley/recaf/ui/pane/ConfigPane.java | 6 +- .../main/resources/translations/en_US.lang | 329 ++++++------ 17 files changed, 758 insertions(+), 463 deletions(-) diff --git a/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java b/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java index 58c01ad5a..4aa0c771e 100644 --- a/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java +++ b/recaf-core/src/main/java/software/coley/recaf/config/BasicConfigContainer.java @@ -15,7 +15,6 @@ public class BasicConfigContainer implements ConfigContainer { private final String group; private final String id; - /** * @param group * Container group. diff --git a/recaf-core/src/main/java/software/coley/recaf/config/ConfigGroups.java b/recaf-core/src/main/java/software/coley/recaf/config/ConfigGroups.java index 2f63c8e89..1635db670 100644 --- a/recaf-core/src/main/java/software/coley/recaf/config/ConfigGroups.java +++ b/recaf-core/src/main/java/software/coley/recaf/config/ConfigGroups.java @@ -33,9 +33,13 @@ public final class ConfigGroups { */ public static final String SERVICE_DEBUG = SERVICE + PACKAGE_SPLIT + "debug"; /** - * Group for decompiler components. + * Group for decompilation components. */ public static final String SERVICE_DECOMPILE = SERVICE + PACKAGE_SPLIT + "decompile"; + /** + * Group for specific decompiler components. + */ + public static final String SERVICE_DECOMPILE_IMPL = SERVICE_DECOMPILE + PACKAGE_SPLIT + "impl"; /** * Group for IO components. */ diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java index 10753f38e..e5b3a11c6 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/AbstractJvmDecompiler.java @@ -62,6 +62,16 @@ public final DecompileResult decompile(@Nonnull Workspace workspace, @Nonnull Jv return result; } + /** + * Takes on the work of {@link #decompile(Workspace, JvmClassInfo)} after the {@link #inputFilters} have been applied to the class. + * + * @param workspace + * Workspace to pull data from. + * @param classInfo + * Class to decompile. + * + * @return Decompilation result. + */ @Nonnull protected abstract DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo classInfo); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java index 58d16317b..153fee991 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/BaseDecompilerConfig.java @@ -3,29 +3,31 @@ import jakarta.annotation.Nonnull; import software.coley.recaf.config.BasicConfigContainer; +import static software.coley.recaf.config.ConfigGroups.SERVICE_DECOMPILE_IMPL; + /** * Base class for fields needed by all decompiler configurations * * @author therathatter */ public class BaseDecompilerConfig extends BasicConfigContainer implements DecompilerConfig { - private int hash = 0; + private int hash = 0; - /** - * @param group Container group. - * @param id Container ID. - */ - public BaseDecompilerConfig(@Nonnull String group, @Nonnull String id) { - super(group, id); - } + /** + * @param id + * Container ID. + */ + public BaseDecompilerConfig( @Nonnull String id) { + super(SERVICE_DECOMPILE_IMPL, id); + } - @Override - public int getHash() { - return hash; - } + @Override + public int getHash() { + return hash; + } - @Override - public void setHash(int hash) { - this.hash = hash; - } + @Override + public void setHash(int hash) { + this.hash = hash; + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java index ab0e2f581..74c15f83f 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/cfr/CfrConfig.java @@ -112,7 +112,7 @@ public class CfrConfig extends BaseDecompilerConfig { @Inject public CfrConfig() { - super(ConfigGroups.SERVICE_DECOMPILE, "decompiler-cfr" + CONFIG_SUFFIX); + super("decompiler-cfr" + CONFIG_SUFFIX); // Add values addValue(new BasicConfigValue<>("stringbuffer", BooleanOption.class, stringbuffer)); addValue(new BasicConfigValue<>("stringbuilder", BooleanOption.class, stringbuilder)); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java index 892ee2e05..a4da216f8 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/procyon/ProcyonConfig.java @@ -42,7 +42,7 @@ public class ProcyonConfig extends BaseDecompilerConfig { private final ObservableObject bytecodeOutputOptions = new ObservableObject<>(BytecodeOutputOptions.createDefault()); @Inject public ProcyonConfig() { - super(ConfigGroups.SERVICE_DECOMPILE, "decompiler-procyon" + CONFIG_SUFFIX); + super("decompiler-procyon" + CONFIG_SUFFIX); addValue(new BasicConfigValue<>("includeLineNumbersInBytecode", boolean.class, includeLineNumbersInBytecode)); addValue(new BasicConfigValue<>("showSyntheticMembers", boolean.class, showSyntheticMembers)); addValue(new BasicConfigValue<>("alwaysGenerateExceptionVariableForCatchBlocks", boolean.class, alwaysGenerateExceptionVariableForCatchBlocks)); diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java index a8b061317..f367a61b2 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/BaseSource.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import org.jetbrains.java.decompiler.main.extern.IContextSource; import software.coley.recaf.path.ClassPathNode; import software.coley.recaf.workspace.model.Workspace; @@ -8,27 +9,31 @@ import java.io.InputStream; /** - * Base Vineflower class/library source + * Base Vineflower class/library source. * * @author therathatter */ public abstract class BaseSource implements IContextSource { - protected final Workspace workspace; + protected final Workspace workspace; - protected BaseSource(Workspace workspace) { - this.workspace = workspace; - } + /** + * @param workspace + * Workspace to pull class files from. + */ + protected BaseSource(@Nonnull Workspace workspace) { + this.workspace = workspace; + } - @Override - public String getName() { - return "Recaf"; - } + @Override + public String getName() { + return "Recaf"; + } - @Override - public InputStream getInputStream(String resource) { - String name = resource.substring(0, resource.length() - IContextSource.CLASS_SUFFIX.length()); - ClassPathNode node = workspace.findClass(name); - if (node == null) return InputStream.nullInputStream(); - return new ByteArrayInputStream(node.getValue().asJvmClass().getBytecode()); - } + @Override + public InputStream getInputStream(String resource) { + String name = resource.substring(0, resource.length() - IContextSource.CLASS_SUFFIX.length()); + ClassPathNode node = workspace.findClass(name); + if (node == null) return InputStream.nullInputStream(); + return new ByteArrayInputStream(node.getValue().asJvmClass().getBytecode()); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java index 92735fdda..0e7abaf09 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/ClassSource.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import org.jetbrains.java.decompiler.main.extern.IResultSaver; import software.coley.recaf.info.InnerClassInfo; import software.coley.recaf.info.JvmClassInfo; @@ -10,41 +11,50 @@ import java.util.List; /** - * Single class source for Vineflower + * Single class source for Vineflower. * * @author Matt Coley * @author therathatter */ public class ClassSource extends BaseSource { - private final JvmClassInfo info; - private final DecompiledOutputSink sink; - - protected ClassSource(Workspace workspace, JvmClassInfo info) { - super(workspace); - this.info = info; - sink = new DecompiledOutputSink(info); - } - - @Override - public Entries getEntries() { - // TODO: Bug in QF/VF makes it so that 'addLibrary' doesn't yield inner info for a class provided with 'addSource' - // So for now until this is fixed upstream we will also supply inners here. - // This will make QF/VF decompile each inner class separately as well, but its the best fix for now without - // too much of a perf hit. - List entries = new ArrayList<>(); - - entries.add(new Entry(info.getName(), Entry.BASE_VERSION)); - for (InnerClassInfo innerClass : info.getInnerClasses()) - entries.add(new Entry(innerClass.getName(), Entry.BASE_VERSION)); - return new Entries(entries, Collections.emptyList(), Collections.emptyList()); - } - - DecompiledOutputSink getSink() { - return this.sink; - } - - @Override - public IOutputSink createOutputSink(IResultSaver saver) { - return sink; - } + private final JvmClassInfo info; + private final DecompiledOutputSink sink; + + /** + * @param workspace + * Workspace to pull class files from. + * @param info + * Target class to decompile. + */ + protected ClassSource(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { + super(workspace); + this.info = info; + sink = new DecompiledOutputSink(info); + } + + /** + * @return Output which holds the decompilation result after the decompilation task completes. + */ + @Nonnull + protected DecompiledOutputSink getSink() { + return sink; + } + + @Override + public Entries getEntries() { + // TODO: Bug in QF/VF makes it so that 'addLibrary' doesn't yield inner info for a class provided with 'addSource' + // So for now until this is fixed upstream we will also supply inners here. + // This will make QF/VF decompile each inner class separately as well, but its the best fix for now without + // too much of a perf hit. + List entries = new ArrayList<>(); + entries.add(new Entry(info.getName(), Entry.BASE_VERSION)); + for (InnerClassInfo innerClass : info.getInnerClasses()) + entries.add(new Entry(innerClass.getName(), Entry.BASE_VERSION)); + return new Entries(entries, Collections.emptyList(), Collections.emptyList()); + } + + @Override + public IOutputSink createOutputSink(IResultSaver saver) { + return sink; + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java index f21d3327e..0ddc62cb6 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DecompiledOutputSink.java @@ -1,50 +1,59 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import org.jetbrains.java.decompiler.main.extern.IContextSource; import software.coley.recaf.info.JvmClassInfo; import java.io.IOException; /** - * Output sink for Vineflower decompiler + * Output sink for Vineflower decompiler. * * @author therathatter */ public class DecompiledOutputSink implements IContextSource.IOutputSink { - protected final JvmClassInfo target; - protected final ThreadLocal out = new ThreadLocal<>(); - - DecompiledOutputSink(JvmClassInfo target) { - this.target = target; - } - - ThreadLocal getDecompiledOutput() { - return out; - } - - @Override - public void begin() { - - } - - @Override - public void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) { - if (target.getName().equals(qualifiedName)) - out.set(content); - } - - @Override - public void acceptDirectory(String directory) { - - } - - @Override - public void acceptOther(String path) { - - } - - @Override - public void close() throws IOException { - - } + protected final JvmClassInfo target; + protected final ThreadLocal out = new ThreadLocal<>(); + + /** + * @param target + * Target class to get output of. + */ + protected DecompiledOutputSink(@Nonnull JvmClassInfo target) { + this.target = target; + } + + /** + * @return Local wrapper of decompilation output. + */ + @Nonnull + protected ThreadLocal getDecompiledOutput() { + return out; + } + + @Override + public void begin() { + // no-op + } + + @Override + public void acceptClass(String qualifiedName, String fileName, String content, int[] mapping) { + if (target.getName().equals(qualifiedName)) + out.set(content); + } + + @Override + public void acceptDirectory(String directory) { + // no-op + } + + @Override + public void acceptOther(String path) { + // no-op + } + + @Override + public void close() throws IOException { + // no-op + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java index 10c25ac00..d94a89cc6 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/DummyResultSaver.java @@ -10,44 +10,43 @@ * @author therathatter */ public class DummyResultSaver implements IResultSaver { - @Override public void saveFolder(String s) { - + // no-op } @Override public void copyFile(String s, String s1, String s2) { - + // no-op } @Override public void saveClassFile(String s, String s1, String s2, String s3, int[] ints) { - + // no-op } @Override public void createArchive(String s, String s1, Manifest manifest) { - + // no-op } @Override public void saveDirEntry(String s, String s1, String s2) { - + // no-op } @Override public void copyEntry(String s, String s1, String s2, String s3) { - + // no-op } @Override public void saveClassEntry(String s, String s1, String s2, String s3, String s4) { - + // no-op } @Override public void closeArchive(String s, String s1) { - + // no-op } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java index aa3b63650..88cc685d6 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/LibrarySource.java @@ -1,5 +1,6 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import software.coley.recaf.workspace.model.Workspace; import software.coley.recaf.workspace.model.resource.WorkspaceResource; @@ -8,23 +9,27 @@ import java.util.stream.Collectors; /** - * Full library source for Vineflower + * Full library source for Vineflower. * * @author Matt Coley * @author therathatter */ public class LibrarySource extends BaseSource { - protected LibrarySource(Workspace workspace) { - super(workspace); - } + /** + * @param workspace + * Workspace to pull class files from. + */ + protected LibrarySource(@Nonnull Workspace workspace) { + super(workspace); + } - @Override - public Entries getEntries() { - List entries = workspace.getAllResources(false).stream() - .map(WorkspaceResource::getJvmClassBundle) - .flatMap(c -> c.keySet().stream()) - .map(className -> new Entry(className, Entry.BASE_VERSION)) - .collect(Collectors.toList()); - return new Entries(entries, Collections.emptyList(), Collections.emptyList()); - } + @Override + public Entries getEntries() { + List entries = workspace.getAllResources(false).stream() + .map(WorkspaceResource::getJvmClassBundle) + .flatMap(c -> c.keySet().stream()) + .map(className -> new Entry(className, Entry.BASE_VERSION)) + .collect(Collectors.toList()); + return new Entries(entries, Collections.emptyList(), Collections.emptyList()); + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java index e322d4643..5ca330600 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java @@ -1,7 +1,9 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; +import org.jetbrains.java.decompiler.main.Fernflower; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import software.coley.observables.ObservableBoolean; import software.coley.recaf.config.BasicConfigValue; @@ -17,121 +19,368 @@ * @author therathatter */ @ApplicationScoped +@SuppressWarnings("all") // ignore unused refs / typos public class VineflowerConfig extends BaseDecompilerConfig { - private final ObservableBoolean REMOVE_BRIDGE = new ObservableBoolean(true); - private final ObservableBoolean REMOVE_SYNTHETIC = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILE_INNER = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILE_CLASS_1_4 = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILE_ASSERTIONS = new ObservableBoolean(true); - private final ObservableBoolean HIDE_EMPTY_SUPER = new ObservableBoolean(true); - private final ObservableBoolean HIDE_DEFAULT_CONSTRUCTOR = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILE_GENERIC_SIGNATURES = new ObservableBoolean(true); - private final ObservableBoolean NO_EXCEPTIONS_RETURN = new ObservableBoolean(true); - private final ObservableBoolean ENSURE_SYNCHRONIZED_MONITOR = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILE_ENUM = new ObservableBoolean(true); - private final ObservableBoolean REMOVE_GET_CLASS_NEW = new ObservableBoolean(true); - private final ObservableBoolean LITERALS_AS_IS = new ObservableBoolean(false); - private final ObservableBoolean BOOLEAN_TRUE_ONE = new ObservableBoolean(true); - private final ObservableBoolean ASCII_STRING_CHARACTERS = new ObservableBoolean(false); - private final ObservableBoolean SYNTHETIC_NOT_SET = new ObservableBoolean(false); - private final ObservableBoolean UNDEFINED_PARAM_TYPE_OBJECT = new ObservableBoolean(true); - private final ObservableBoolean USE_DEBUG_VAR_NAMES = new ObservableBoolean(true); - private final ObservableBoolean USE_METHOD_PARAMETERS = new ObservableBoolean(true); - private final ObservableBoolean REMOVE_EMPTY_RANGES = new ObservableBoolean(true); - private final ObservableBoolean FINALLY_DEINLINE = new ObservableBoolean(true); - private final ObservableBoolean IDEA_NOT_NULL_ANNOTATION = new ObservableBoolean(true); - private final ObservableBoolean LAMBDA_TO_ANONYMOUS_CLASS = new ObservableBoolean(false); - private final ObservableBoolean BYTECODE_SOURCE_MAPPING = new ObservableBoolean(false); - private final ObservableBoolean DUMP_CODE_LINES = new ObservableBoolean(false); - private final ObservableBoolean IGNORE_INVALID_BYTECODE = new ObservableBoolean(false); - private final ObservableBoolean VERIFY_ANONYMOUS_CLASSES = new ObservableBoolean(false); - private final ObservableBoolean TERNARY_CONSTANT_SIMPLIFICATION = new ObservableBoolean(false); - private final ObservableBoolean OVERRIDE_ANNOTATION = new ObservableBoolean(true); - private final ObservableBoolean PATTERN_MATCHING = new ObservableBoolean(true); - private final ObservableBoolean TRY_LOOP_FIX = new ObservableBoolean(true); - private final ObservableBoolean TERNARY_CONDITIONS= new ObservableBoolean(true); - private final ObservableBoolean SWITCH_EXPRESSIONS = new ObservableBoolean(true); - private final ObservableBoolean SHOW_HIDDEN_STATEMENTS = new ObservableBoolean(false); - private final ObservableBoolean SIMPLIFY_STACK_SECOND_PASS = new ObservableBoolean(true); - private final ObservableBoolean VERIFY_VARIABLE_MERGES = new ObservableBoolean(false); - private final ObservableBoolean DECOMPILE_PREVIEW = new ObservableBoolean(true); - private final ObservableBoolean EXPLICIT_GENERIC_ARGUMENTS = new ObservableBoolean(false); - private final ObservableBoolean INLINE_SIMPLE_LAMBDAS = new ObservableBoolean(true); - private final ObservableBoolean USE_JAD_VARNAMING = new ObservableBoolean(false); - private final ObservableBoolean USE_JAD_PARAMETER_NAMING = new ObservableBoolean(false); - private final ObservableBoolean SKIP_EXTRA_FILES = new ObservableBoolean(false); - private final ObservableBoolean WARN_INCONSISTENT_INNER_CLASSES = new ObservableBoolean(true); - private final ObservableBoolean DUMP_BYTECODE_ON_ERROR = new ObservableBoolean(true); - private final ObservableBoolean DUMP_EXCEPTION_ON_ERROR = new ObservableBoolean(true); - private final ObservableBoolean DECOMPILER_COMMENTS = new ObservableBoolean(true); - private final ObservableBoolean SOURCE_FILE_COMMENTS = new ObservableBoolean(false); - private final ObservableBoolean DECOMPILE_COMPLEX_CONDYS = new ObservableBoolean(false); - private final ObservableBoolean FORCE_JSR_INLINE = new ObservableBoolean(false); - - @Inject - public VineflowerConfig() { - super(ConfigGroups.SERVICE_DECOMPILE, "decompiler-vineflower" + CONFIG_SUFFIX); - addValue(new BasicConfigValue<>( "rbr", boolean.class, REMOVE_BRIDGE)); - addValue(new BasicConfigValue<>( "rsy", boolean.class, REMOVE_SYNTHETIC)); - addValue(new BasicConfigValue<>( "din", boolean.class, DECOMPILE_INNER)); - addValue(new BasicConfigValue<>( "dc4", boolean.class, DECOMPILE_CLASS_1_4)); - addValue(new BasicConfigValue<>( "das", boolean.class, DECOMPILE_ASSERTIONS)); - addValue(new BasicConfigValue<>( "hes", boolean.class, HIDE_EMPTY_SUPER)); - addValue(new BasicConfigValue<>( "hdc", boolean.class, HIDE_DEFAULT_CONSTRUCTOR)); - addValue(new BasicConfigValue<>( "dgs", boolean.class, DECOMPILE_GENERIC_SIGNATURES)); - addValue(new BasicConfigValue<>( "ner", boolean.class, NO_EXCEPTIONS_RETURN)); - addValue(new BasicConfigValue<>( "esm", boolean.class, ENSURE_SYNCHRONIZED_MONITOR)); - addValue(new BasicConfigValue<>( "den", boolean.class, DECOMPILE_ENUM)); - addValue(new BasicConfigValue<>( "dpr", boolean.class, DECOMPILE_PREVIEW)); - addValue(new BasicConfigValue<>( "rgn", boolean.class, REMOVE_GET_CLASS_NEW)); - addValue(new BasicConfigValue<>( "lit", boolean.class, LITERALS_AS_IS)); - addValue(new BasicConfigValue<>( "bto", boolean.class, BOOLEAN_TRUE_ONE)); - addValue(new BasicConfigValue<>( "asc", boolean.class, ASCII_STRING_CHARACTERS)); - addValue(new BasicConfigValue<>( "nns", boolean.class, SYNTHETIC_NOT_SET)); - addValue(new BasicConfigValue<>( "uto", boolean.class, UNDEFINED_PARAM_TYPE_OBJECT)); - addValue(new BasicConfigValue<>( "udv", boolean.class, USE_DEBUG_VAR_NAMES)); - addValue(new BasicConfigValue<>( "ump", boolean.class, USE_METHOD_PARAMETERS)); - addValue(new BasicConfigValue<>( "rer", boolean.class, REMOVE_EMPTY_RANGES)); - addValue(new BasicConfigValue<>( "fdi", boolean.class, FINALLY_DEINLINE)); - addValue(new BasicConfigValue<>( "inn", boolean.class, IDEA_NOT_NULL_ANNOTATION)); - addValue(new BasicConfigValue<>( "lac", boolean.class, LAMBDA_TO_ANONYMOUS_CLASS)); - addValue(new BasicConfigValue<>( "bsm", boolean.class, BYTECODE_SOURCE_MAPPING)); - addValue(new BasicConfigValue<>( "dcl", boolean.class, DUMP_CODE_LINES)); - addValue(new BasicConfigValue<>( "iib", boolean.class, IGNORE_INVALID_BYTECODE)); - addValue(new BasicConfigValue<>( "vac", boolean.class, VERIFY_ANONYMOUS_CLASSES)); - addValue(new BasicConfigValue<>( "tcs", boolean.class, TERNARY_CONSTANT_SIMPLIFICATION)); - addValue(new BasicConfigValue<>( "pam", boolean.class, PATTERN_MATCHING)); - addValue(new BasicConfigValue<>( "tlf", boolean.class, TRY_LOOP_FIX)); - addValue(new BasicConfigValue<>( "tco", boolean.class, TERNARY_CONDITIONS)); - addValue(new BasicConfigValue<>( "swe", boolean.class, SWITCH_EXPRESSIONS)); - addValue(new BasicConfigValue<>( "shs", boolean.class, SHOW_HIDDEN_STATEMENTS)); - addValue(new BasicConfigValue<>( "ovr", boolean.class, OVERRIDE_ANNOTATION)); - addValue(new BasicConfigValue<>( "ssp", boolean.class, SIMPLIFY_STACK_SECOND_PASS)); - addValue(new BasicConfigValue<>( "vvm", boolean.class, VERIFY_VARIABLE_MERGES)); - addValue(new BasicConfigValue<>( "ega", boolean.class, EXPLICIT_GENERIC_ARGUMENTS)); - addValue(new BasicConfigValue<>( "isl", boolean.class, INLINE_SIMPLE_LAMBDAS)); - addValue(new BasicConfigValue<>( "jvn", boolean.class, USE_JAD_VARNAMING)); - addValue(new BasicConfigValue<>( "jpr", boolean.class, USE_JAD_PARAMETER_NAMING)); - addValue(new BasicConfigValue<>( "sef", boolean.class, SKIP_EXTRA_FILES)); - addValue(new BasicConfigValue<>( "win", boolean.class, WARN_INCONSISTENT_INNER_CLASSES)); - addValue(new BasicConfigValue<>( "dbe", boolean.class, DUMP_BYTECODE_ON_ERROR)); - addValue(new BasicConfigValue<>( "dee", boolean.class, DUMP_EXCEPTION_ON_ERROR)); - addValue(new BasicConfigValue<>( "dec", boolean.class, DECOMPILER_COMMENTS)); - addValue(new BasicConfigValue<>( "sfc", boolean.class, SOURCE_FILE_COMMENTS)); - addValue(new BasicConfigValue<>( "dcc", boolean.class, DECOMPILE_COMPLEX_CONDYS)); - addValue(new BasicConfigValue<>( "fji", boolean.class, FORCE_JSR_INLINE)); - registerConfigValuesHashUpdates(); - } - - Map getFernflowerProperties() { - Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); - - getValues().forEach((key, value) -> { - if (value.getValue() instanceof Boolean) { - properties.put(key, (Boolean)value.getValue() ? "1" : "0"); - } - }); - - return properties; - } + private final ObservableBoolean removeBridge = new ObservableBoolean(true); + private final ObservableBoolean removeSynthetic = new ObservableBoolean(true); + private final ObservableBoolean decompileInner = new ObservableBoolean(true); + private final ObservableBoolean decompileClass_1_4 = new ObservableBoolean(true); + private final ObservableBoolean decompileAssertions = new ObservableBoolean(true); + private final ObservableBoolean hideEmptySuper = new ObservableBoolean(true); + private final ObservableBoolean hideDefaultConstructor = new ObservableBoolean(true); + private final ObservableBoolean decompileGenericSignatures = new ObservableBoolean(true); + private final ObservableBoolean noExceptionsReturn = new ObservableBoolean(true); + private final ObservableBoolean ensureSynchronizedMonitor = new ObservableBoolean(true); + private final ObservableBoolean decompileEnum = new ObservableBoolean(true); + private final ObservableBoolean removeGetClassNew = new ObservableBoolean(true); + private final ObservableBoolean literalsAsIs = new ObservableBoolean(false); + private final ObservableBoolean booleanTrueOne = new ObservableBoolean(true); + private final ObservableBoolean asciiStringCharacters = new ObservableBoolean(false); + private final ObservableBoolean syntheticNotSet = new ObservableBoolean(false); + private final ObservableBoolean undefinedParamTypeObject = new ObservableBoolean(true); + private final ObservableBoolean useDebugVarNames = new ObservableBoolean(true); + private final ObservableBoolean useMethodParameters = new ObservableBoolean(true); + private final ObservableBoolean removeEmptyRanges = new ObservableBoolean(true); + private final ObservableBoolean finallyDeinline = new ObservableBoolean(true); + private final ObservableBoolean ideaNotNullAnnotation = new ObservableBoolean(true); + private final ObservableBoolean lambdaToAnonymousClass = new ObservableBoolean(false); + private final ObservableBoolean bytecodeSourceMapping = new ObservableBoolean(false); + private final ObservableBoolean dumpCodeLines = new ObservableBoolean(false); + private final ObservableBoolean ignoreInvalidBytecode = new ObservableBoolean(false); + private final ObservableBoolean verifyAnonymousClasses = new ObservableBoolean(false); + private final ObservableBoolean ternaryConstantSimplification = new ObservableBoolean(false); + private final ObservableBoolean overrideAnnotation = new ObservableBoolean(true); + private final ObservableBoolean patternMatching = new ObservableBoolean(true); + private final ObservableBoolean tryLoopFix = new ObservableBoolean(true); + private final ObservableBoolean ternaryConditions = new ObservableBoolean(true); + private final ObservableBoolean switchExpressions = new ObservableBoolean(true); + private final ObservableBoolean showHiddenStatements = new ObservableBoolean(false); + private final ObservableBoolean simplifyStackSecondPass = new ObservableBoolean(true); + private final ObservableBoolean verifyVariableMerges = new ObservableBoolean(false); + private final ObservableBoolean decompilePreview = new ObservableBoolean(true); + private final ObservableBoolean explicitGenericArguments = new ObservableBoolean(false); + private final ObservableBoolean inlineSimpleLambdas = new ObservableBoolean(true); + private final ObservableBoolean useJadVarNaming = new ObservableBoolean(false); + private final ObservableBoolean useJadParameterNaming = new ObservableBoolean(false); + private final ObservableBoolean skipExtraFiles = new ObservableBoolean(false); + private final ObservableBoolean warnInconsistentInnerClasses = new ObservableBoolean(true); + private final ObservableBoolean dumpBytecodeOnError = new ObservableBoolean(true); + private final ObservableBoolean dumpExceptionOnError = new ObservableBoolean(true); + private final ObservableBoolean decompilerComments = new ObservableBoolean(false); + private final ObservableBoolean sourceFileComments = new ObservableBoolean(false); + private final ObservableBoolean decompileComplexCondys = new ObservableBoolean(false); + private final ObservableBoolean forceJsrInline = new ObservableBoolean(false); + + @Inject + public VineflowerConfig() { + super("decompiler-vineflower" + CONFIG_SUFFIX); + addValue(new BasicConfigValue<>("rbr", boolean.class, removeBridge)); + addValue(new BasicConfigValue<>("rsy", boolean.class, removeSynthetic)); + addValue(new BasicConfigValue<>("din", boolean.class, decompileInner)); + addValue(new BasicConfigValue<>("dc4", boolean.class, decompileClass_1_4)); + addValue(new BasicConfigValue<>("das", boolean.class, decompileAssertions)); + addValue(new BasicConfigValue<>("hes", boolean.class, hideEmptySuper)); + addValue(new BasicConfigValue<>("hdc", boolean.class, hideDefaultConstructor)); + addValue(new BasicConfigValue<>("dgs", boolean.class, decompileGenericSignatures)); + addValue(new BasicConfigValue<>("ner", boolean.class, noExceptionsReturn)); + addValue(new BasicConfigValue<>("esm", boolean.class, ensureSynchronizedMonitor)); + addValue(new BasicConfigValue<>("den", boolean.class, decompileEnum)); + addValue(new BasicConfigValue<>("dpr", boolean.class, decompilePreview)); + addValue(new BasicConfigValue<>("rgn", boolean.class, removeGetClassNew)); + addValue(new BasicConfigValue<>("lit", boolean.class, literalsAsIs)); + addValue(new BasicConfigValue<>("bto", boolean.class, booleanTrueOne)); + addValue(new BasicConfigValue<>("asc", boolean.class, asciiStringCharacters)); + addValue(new BasicConfigValue<>("nns", boolean.class, syntheticNotSet)); + addValue(new BasicConfigValue<>("uto", boolean.class, undefinedParamTypeObject)); + addValue(new BasicConfigValue<>("udv", boolean.class, useDebugVarNames)); + addValue(new BasicConfigValue<>("ump", boolean.class, useMethodParameters)); + addValue(new BasicConfigValue<>("rer", boolean.class, removeEmptyRanges)); + addValue(new BasicConfigValue<>("fdi", boolean.class, finallyDeinline)); + addValue(new BasicConfigValue<>("inn", boolean.class, ideaNotNullAnnotation)); + addValue(new BasicConfigValue<>("lac", boolean.class, lambdaToAnonymousClass)); + addValue(new BasicConfigValue<>("bsm", boolean.class, bytecodeSourceMapping)); + addValue(new BasicConfigValue<>("dcl", boolean.class, dumpCodeLines)); + addValue(new BasicConfigValue<>("iib", boolean.class, ignoreInvalidBytecode)); + addValue(new BasicConfigValue<>("vac", boolean.class, verifyAnonymousClasses)); + addValue(new BasicConfigValue<>("tcs", boolean.class, ternaryConstantSimplification)); + addValue(new BasicConfigValue<>("pam", boolean.class, patternMatching)); + addValue(new BasicConfigValue<>("tlf", boolean.class, tryLoopFix)); + addValue(new BasicConfigValue<>("tco", boolean.class, ternaryConditions)); + addValue(new BasicConfigValue<>("swe", boolean.class, switchExpressions)); + addValue(new BasicConfigValue<>("shs", boolean.class, showHiddenStatements)); + addValue(new BasicConfigValue<>("ovr", boolean.class, overrideAnnotation)); + addValue(new BasicConfigValue<>("ssp", boolean.class, simplifyStackSecondPass)); + addValue(new BasicConfigValue<>("vvm", boolean.class, verifyVariableMerges)); + addValue(new BasicConfigValue<>("ega", boolean.class, explicitGenericArguments)); + addValue(new BasicConfigValue<>("isl", boolean.class, inlineSimpleLambdas)); + addValue(new BasicConfigValue<>("jvn", boolean.class, useJadVarNaming)); + addValue(new BasicConfigValue<>("jpr", boolean.class, useJadParameterNaming)); + addValue(new BasicConfigValue<>("sef", boolean.class, skipExtraFiles)); + addValue(new BasicConfigValue<>("win", boolean.class, warnInconsistentInnerClasses)); + addValue(new BasicConfigValue<>("dbe", boolean.class, dumpBytecodeOnError)); + addValue(new BasicConfigValue<>("dee", boolean.class, dumpExceptionOnError)); + addValue(new BasicConfigValue<>("dec", boolean.class, decompilerComments)); + addValue(new BasicConfigValue<>("sfc", boolean.class, sourceFileComments)); + addValue(new BasicConfigValue<>("dcc", boolean.class, decompileComplexCondys)); + addValue(new BasicConfigValue<>("fji", boolean.class, forceJsrInline)); + registerConfigValuesHashUpdates(); + } + + /** + * @return Map of values to pass to the {@link Fernflower} instance. + */ + @Nonnull + protected Map getFernflowerProperties() { + Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); + getValues().forEach((key, value) -> { + if (value.getValue() instanceof Boolean bool) + properties.put(key, bool ? "1" : "0"); + }); + return properties; + } + + @Nonnull + public ObservableBoolean getRemoveBridge() { + return removeBridge; + } + + @Nonnull + public ObservableBoolean getRemoveSynthetic() { + return removeSynthetic; + } + + @Nonnull + public ObservableBoolean getDecompileInner() { + return decompileInner; + } + + @Nonnull + public ObservableBoolean getDecompileClass_1_4() { + return decompileClass_1_4; + } + + @Nonnull + public ObservableBoolean getDecompileAssertions() { + return decompileAssertions; + } + + @Nonnull + public ObservableBoolean getHideEmptySuper() { + return hideEmptySuper; + } + + @Nonnull + public ObservableBoolean getHideDefaultConstructor() { + return hideDefaultConstructor; + } + + @Nonnull + public ObservableBoolean getDecompileGenericSignatures() { + return decompileGenericSignatures; + } + + @Nonnull + public ObservableBoolean getNoExceptionsReturn() { + return noExceptionsReturn; + } + + @Nonnull + public ObservableBoolean getEnsureSynchronizedMonitor() { + return ensureSynchronizedMonitor; + } + + @Nonnull + public ObservableBoolean getDecompileEnum() { + return decompileEnum; + } + + @Nonnull + public ObservableBoolean getRemoveGetClassNew() { + return removeGetClassNew; + } + + @Nonnull + public ObservableBoolean getLiteralsAsIs() { + return literalsAsIs; + } + + @Nonnull + public ObservableBoolean getBooleanTrueOne() { + return booleanTrueOne; + } + + @Nonnull + public ObservableBoolean getAsciiStringCharacters() { + return asciiStringCharacters; + } + + @Nonnull + public ObservableBoolean getSyntheticNotSet() { + return syntheticNotSet; + } + + @Nonnull + public ObservableBoolean getUndefinedParamTypeObject() { + return undefinedParamTypeObject; + } + + @Nonnull + public ObservableBoolean getUseDebugVarNames() { + return useDebugVarNames; + } + + @Nonnull + public ObservableBoolean getUseMethodParameters() { + return useMethodParameters; + } + + @Nonnull + public ObservableBoolean getRemoveEmptyRanges() { + return removeEmptyRanges; + } + + @Nonnull + public ObservableBoolean getFinallyDeinline() { + return finallyDeinline; + } + + @Nonnull + public ObservableBoolean getIdeaNotNullAnnotation() { + return ideaNotNullAnnotation; + } + + @Nonnull + public ObservableBoolean getLambdaToAnonymousClass() { + return lambdaToAnonymousClass; + } + + @Nonnull + public ObservableBoolean getBytecodeSourceMapping() { + return bytecodeSourceMapping; + } + + @Nonnull + public ObservableBoolean getDumpCodeLines() { + return dumpCodeLines; + } + + @Nonnull + public ObservableBoolean getIgnoreInvalidBytecode() { + return ignoreInvalidBytecode; + } + + @Nonnull + public ObservableBoolean getVerifyAnonymousClasses() { + return verifyAnonymousClasses; + } + + @Nonnull + public ObservableBoolean getTernaryConstantSimplification() { + return ternaryConstantSimplification; + } + + @Nonnull + public ObservableBoolean getOverrideAnnotation() { + return overrideAnnotation; + } + + @Nonnull + public ObservableBoolean getPatternMatching() { + return patternMatching; + } + + @Nonnull + public ObservableBoolean getTryLoopFix() { + return tryLoopFix; + } + + @Nonnull + public ObservableBoolean getTernaryConditions() { + return ternaryConditions; + } + + @Nonnull + public ObservableBoolean getSwitchExpressions() { + return switchExpressions; + } + + @Nonnull + public ObservableBoolean getShowHiddenStatements() { + return showHiddenStatements; + } + + @Nonnull + public ObservableBoolean getSimplifyStackSecondPass() { + return simplifyStackSecondPass; + } + + @Nonnull + public ObservableBoolean getVerifyVariableMerges() { + return verifyVariableMerges; + } + + @Nonnull + public ObservableBoolean getDecompilePreview() { + return decompilePreview; + } + + @Nonnull + public ObservableBoolean getExplicitGenericArguments() { + return explicitGenericArguments; + } + + @Nonnull + public ObservableBoolean getInlineSimpleLambdas() { + return inlineSimpleLambdas; + } + + @Nonnull + public ObservableBoolean getUseJadVarNaming() { + return useJadVarNaming; + } + + @Nonnull + public ObservableBoolean getUseJadParameterNaming() { + return useJadParameterNaming; + } + + @Nonnull + public ObservableBoolean getSkipExtraFiles() { + return skipExtraFiles; + } + + @Nonnull + public ObservableBoolean getWarnInconsistentInnerClasses() { + return warnInconsistentInnerClasses; + } + + @Nonnull + public ObservableBoolean getDumpBytecodeOnError() { + return dumpBytecodeOnError; + } + + @Nonnull + public ObservableBoolean getDumpExceptionOnError() { + return dumpExceptionOnError; + } + + @Nonnull + public ObservableBoolean getDecompilerComments() { + return decompilerComments; + } + + @Nonnull + public ObservableBoolean getSourceFileComments() { + return sourceFileComments; + } + + @Nonnull + public ObservableBoolean getDecompileComplexCondys() { + return decompileComplexCondys; + } + + @Nonnull + public ObservableBoolean getForceJsrInline() { + return forceJsrInline; + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java index c1a9c8f40..d536dc20f 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java @@ -18,43 +18,44 @@ */ @ApplicationScoped public class VineflowerDecompiler extends AbstractJvmDecompiler { - public static final String NAME = "Vineflower"; - private final VineflowerConfig config; - private final IFernflowerLogger logger = new VineflowerLogger(); - private final IResultSaver dummySaver = new DummyResultSaver(); - - /** - * New Vineflower decompiler instance. - * - * @param config Decompiler configuration. - */ - @Inject - public VineflowerDecompiler(@Nonnull VineflowerConfig config) { - // Change this version to be dynamic when / if the Vineflower authors make a function that returns the version... - super(NAME, "1.9.3", config); - this.config = config; - } - - @Nonnull - @Override - public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { - Fernflower fernflower = new Fernflower(dummySaver, config.getFernflowerProperties(), logger); - - try { - ClassSource source = new ClassSource(workspace, info); - fernflower.addSource(source); - fernflower.addLibrary(new LibrarySource(workspace)); - fernflower.decompileContext(); - - String decompiled = source.getSink().getDecompiledOutput().get(); - - if (decompiled == null || decompiled.isEmpty()) { - return new DecompileResult(new IllegalStateException("Missing decompilation output"), config.getHash()); - } - - return new DecompileResult(decompiled, config.getHash()); - } catch (Exception e) { - return new DecompileResult(e, config.getHash()); - } - } + public static final String NAME = "Vineflower"; + private final VineflowerConfig config; + private final IFernflowerLogger logger = new VineflowerLogger(); + private final IResultSaver dummySaver = new DummyResultSaver(); + + /** + * New Vineflower decompiler instance. + * + * @param config + * Decompiler configuration. + */ + @Inject + public VineflowerDecompiler(@Nonnull VineflowerConfig config) { + // Change this version to be dynamic when / if the Vineflower authors make a function that returns the version... + super(NAME, "1.9.3", config); + this.config = config; + } + + @Nonnull + @Override + public DecompileResult decompileInternal(@Nonnull Workspace workspace, @Nonnull JvmClassInfo info) { + Fernflower fernflower = new Fernflower(dummySaver, config.getFernflowerProperties(), logger); + + try { + ClassSource source = new ClassSource(workspace, info); + fernflower.addSource(source); + fernflower.addLibrary(new LibrarySource(workspace)); + fernflower.decompileContext(); + + String decompiled = source.getSink().getDecompiledOutput().get(); + + if (decompiled == null || decompiled.isEmpty()) { + return new DecompileResult(new IllegalStateException("Missing decompilation output"), config.getHash()); + } + + return new DecompileResult(decompiled, config.getHash()); + } catch (Exception e) { + return new DecompileResult(e, config.getHash()); + } + } } diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java index f2eb0854e..d3fef696a 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java @@ -10,20 +10,20 @@ * @author therathatter */ public class VineflowerLogger extends IFernflowerLogger { - private static final Logger logger = Logging.get(VineflowerLogger.class); + private static final Logger logger = Logging.get(VineflowerLogger.class); - @Override - public void writeMessage(String s, Severity severity) { - switch (severity) { - case TRACE -> logger.trace(s); - case INFO -> logger.info(s); - case WARN -> logger.warn(s); - case ERROR -> logger.error(s); - } - } + @Override + public void writeMessage(String s, Severity severity) { + switch (severity) { + case TRACE -> logger.trace(s); + case INFO -> logger.info(s); + case WARN -> logger.warn(s); + case ERROR -> logger.error(s); + } + } - @Override - public void writeMessage(String s, Severity severity, Throwable throwable) { - writeMessage(s, severity); - } + @Override + public void writeMessage(String s, Severity severity, Throwable throwable) { + writeMessage(s, severity); + } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManager.java b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManager.java index b568a5756..e8d1a0f08 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManager.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/config/ConfigIconManager.java @@ -35,6 +35,7 @@ public ConfigIconManager(@Nonnull ConfigIconManagerConfig config) { // Add defaults registerGroup(SERVICE, CarbonIcons.DATA_CLASS); registerGroup(SERVICE_ANALYSIS, CarbonIcons.COGNITIVE); + registerGroup(SERVICE_ASSEMBLER, CarbonIcons.CODE); registerGroup(SERVICE_COMPILE, CarbonIcons.CODE); registerGroup(SERVICE_DEBUG, CarbonIcons.DEBUG); registerGroup(SERVICE_DECOMPILE, CarbonIcons.CODE); diff --git a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java index 46dadb760..52a3488c2 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java +++ b/recaf-ui/src/main/java/software/coley/recaf/ui/pane/ConfigPane.java @@ -166,7 +166,7 @@ private TreeItem getChildTreeItemByName(@Nonnull TreeItem item, * @return Tree item if it exists. Otherwise {@code null}. */ @Nullable - private TreeItem getItem(ConfigContainer container, boolean createIfMissing) { + private TreeItem getItem(@Nonnull ConfigContainer container, boolean createIfMissing) { TreeItem currentItem = root; String currentPackage = null; String[] packages = getGroupPackages(container); @@ -205,7 +205,7 @@ private TreeItem getItem(ConfigContainer container, boolean createIfMiss */ private class ConfigPage extends GridPane { @SuppressWarnings({"rawtypes", "unchecked"}) - private ConfigPage(ConfigContainer container) { + private ConfigPage(@Nonnull ConfigContainer container) { // Plugin configs are given special treatment. // They are not expected to install additional translations, so their ID's will be used as literal names. boolean isThirdPartyConfig = ConfigGroups.EXTERNAL.equals(container.getGroup()); @@ -253,7 +253,7 @@ private ConfigPage(ConfigContainer container) { * Page to show child-pages when the group itself does not have content. */ private class MissingPage extends VBox { - public MissingPage(String id) { + public MissingPage(@Nonnull String id) { // Title ObservableList children = getChildren(); Label title = new BoundLabel(getBinding(id)); diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index 6fd19fee6..07ba3a05b 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -451,173 +451,174 @@ service.debug.attach-config.attach-jmx-bean-agent=Attach JMX bean agent service.debug.attach-config.passive-scanning=Passive scanning state service.config-manager-config=Config manager service.decompile=Decompilation -service.decompile.decompiler-cfr-config=CFR -service.decompile.decompiler-cfr-config.aexagg=Try to extend and merge exceptions more aggressively -service.decompile.decompiler-cfr-config.aexagg2=Try to extend and merge exceptions more aggressively (may change semantics) -service.decompile.decompiler-cfr-config.aggressivedocopy=Clone code from impossible jumps into loops with 'first' test -service.decompile.decompiler-cfr-config.aggressivedoextension=Fold impossible jumps into do loops with 'first' test -service.decompile.decompiler-cfr-config.aggressiveduff=Fold duff device style switches with additional control. -service.decompile.decompiler-cfr-config.aggressivesizethreshold=Opcode count at which to trigger aggressive reductions -service.decompile.decompiler-cfr-config.allowmalformedswitch=Allow potentially malformed switch statements -service.decompile.decompiler-cfr-config.antiobf=Undo various obfuscations -service.decompile.decompiler-cfr-config.arrayiter=Re-sugar array based iteration -service.decompile.decompiler-cfr-config.collectioniter=Re-sugar collection based iteration -service.decompile.decompiler-cfr-config.commentmonitors=Replace monitors with comments - useful if we're completely confused -service.decompile.decompiler-cfr-config.comments=Output comments describing decompiler status, fallback flags etc. -service.decompile.decompiler-cfr-config.constobf=Undo constant obfuscation -service.decompile.decompiler-cfr-config.decodeenumswitch=Re-sugar switch on enum -service.decompile.decompiler-cfr-config.decodefinally=Re-sugar finally statements -service.decompile.decompiler-cfr-config.decodelambdas=Re-build lambda functions -service.decompile.decompiler-cfr-config.decodestringswitch=Re-sugar switch on String -service.decompile.decompiler-cfr-config.eclipse=Enable transformations to handle Eclipse code better -service.decompile.decompiler-cfr-config.elidescala=Elide things which aren't helpful in scala output (serialVersionUID, @ScalaSignature) -service.decompile.decompiler-cfr-config.forbidanonymousclasses=Don't allow anonymous classes. -service.decompile.decompiler-cfr-config.forbidmethodscopedclasses=Don't allow method scoped classes. -service.decompile.decompiler-cfr-config.forceclassfilever=Force the version of the classfile (and hence java) that classfiles are decompiled as. -service.decompile.decompiler-cfr-config.forcecondpropagate=Pull results of deterministic jumps back through some constant assignments -service.decompile.decompiler-cfr-config.forceexceptionprune=Remove nested exception handlers if they don't change semantics -service.decompile.decompiler-cfr-config.forcereturningifs=Move return up to jump site -service.decompile.decompiler-cfr-config.forcetopsort=Force basic block sorting. Usually only useful when obfuscation is present. -service.decompile.decompiler-cfr-config.forcetopsortaggress=Force extra aggressive topsort options -service.decompile.decompiler-cfr-config.forcetopsortnopull=Force topsort not to pull try blocks -service.decompile.decompiler-cfr-config.forloopaggcapture=Allow for loops to aggressively roll mutations into update section, even if they don't appear to be involved with the predicate -service.decompile.decompiler-cfr-config.hidebridgemethods=Hide bridge methods -service.decompile.decompiler-cfr-config.hidelangimports=Hide imports from java.lang. -service.decompile.decompiler-cfr-config.hidelongstrings=Hide very long strings - useful if obfuscators have placed fake code in strings -service.decompile.decompiler-cfr-config.hideutf=Hide UTF8 characters - quote them instead of showing the raw characters -service.decompile.decompiler-cfr-config.ignoreexceptions=Drop exception information if completely stuck (WARNING : changes semantics, dangerous!) -service.decompile.decompiler-cfr-config.ignoreexceptionsalways=Drop exception information (WARNING : changes semantics, dangerous!) -service.decompile.decompiler-cfr-config.innerclasses=Decompile inner classes -service.decompile.decompiler-cfr-config.instanceofpattern=Re-sugar instanceof pattern matches -service.decompile.decompiler-cfr-config.j14classobj=Reverse java 1.4 class object construction -service.decompile.decompiler-cfr-config.labelledblocks=Allow code to be emitted which uses labelled blocks, (handling odd forward gotos) -service.decompile.decompiler-cfr-config.lenient=Be a bit more lenient in situations where we'd normally throw an exception -service.decompile.decompiler-cfr-config.liftconstructorinit=Lift initialisation code common to all constructors into member initialisation -service.decompile.decompiler-cfr-config.obfattr=Undo attribute obfuscation -service.decompile.decompiler-cfr-config.obfcontrol=Undo control flow obfuscation -service.decompile.decompiler-cfr-config.override=Generate @Override annotations (if method is seen to implement interface method, or override a base class method) -service.decompile.decompiler-cfr-config.previewfeatures=Decompile preview features if class was compiled with 'javac --enable-preview' -service.decompile.decompiler-cfr-config.pullcodecase=Pull code into case statements agressively -service.decompile.decompiler-cfr-config.recordtypes=Re-sugar record types -service.decompile.decompiler-cfr-config.recover=Allow more and more aggressive options to be set if decompilation fails -service.decompile.decompiler-cfr-config.recovertypeclash=Split lifetimes where analysis caused type clash -service.decompile.decompiler-cfr-config.recovertypehints=Recover type hints for iterators from first pass -service.decompile.decompiler-cfr-config.reducecondscope=Reduce the scope of conditionals, possibly generating more anonymous blocks -service.decompile.decompiler-cfr-config.relinkconst=Relink constants - if there is an inlined reference to a field, attempt to de-inline. -service.decompile.decompiler-cfr-config.relinkconststring=Relink constant strings - if there is a local reference to a string which matches a static final, use the static final. -service.decompile.decompiler-cfr-config.removebadgenerics=Hide generics where we've obviously got it wrong, and fallback to non-generic -service.decompile.decompiler-cfr-config.removeboilerplate=Remove boilderplate functions - constructor boilerplate, lambda deserialisation etc. -service.decompile.decompiler-cfr-config.removedeadconditionals=Remove code that can't be executed. -service.decompile.decompiler-cfr-config.removedeadmethods=Remove pointless methods - default constructor etc. -service.decompile.decompiler-cfr-config.removeinnerclasssynthetics=Remove (where possible) implicit outer class references in inner classes -service.decompile.decompiler-cfr-config.renamedupmembers=Rename ambiguous/duplicate fields. -service.decompile.decompiler-cfr-config.renameenumidents=Rename ENUM identifiers which do not match their 'expected' string names. -service.decompile.decompiler-cfr-config.renameillegalidents=Rename identifiers which are not valid java identifiers. -service.decompile.decompiler-cfr-config.renamesmallmembers=Rename small members. Note - this WILL break reflection based access, so is not automatically enabled. -service.decompile.decompiler-cfr-config.sealed=Decompile 'sealed' constructs -service.decompile.decompiler-cfr-config.showinferrable=Decorate methods with explicit types if not implied by arguments -service.decompile.decompiler-cfr-config.showversion=Show used CFR version in header (handy to turn off when regression testing) -service.decompile.decompiler-cfr-config.skipbatchinnerclasses=When processing many files, skip inner classes, as they will be processed as part of outer classes anyway. -service.decompile.decompiler-cfr-config.staticinitreturn=Try to remove return from static init -service.decompile.decompiler-cfr-config.stringbuffer=Convert new StringBuffer().append.append.append to string + string + string -service.decompile.decompiler-cfr-config.stringbuilder=Convert new StringBuilder().append.append.append to string + string + string -service.decompile.decompiler-cfr-config.stringconcat=Convert usages of StringConcatFactor to string + string + string -service.decompile.decompiler-cfr-config.sugarasserts=Re-sugar assert calls -service.decompile.decompiler-cfr-config.sugarboxing=Where possible, remove pointless boxing wrappers -service.decompile.decompiler-cfr-config.sugarenums=Re-sugar enums -service.decompile.decompiler-cfr-config.sugarretrolambda=Where possible, resugar uses of retro lambda -service.decompile.decompiler-cfr-config.switchexpression=Re-sugar switch expression -service.decompile.decompiler-cfr-config.tidymonitors=Remove support code for monitors - e.g. catch blocks just to exit a monitor -service.decompile.decompiler-cfr-config.tryresources=Reconstruct try-with-resources -service.decompile.decompiler-cfr-config.usenametable=Use local variable name table if present -service.decompile.decompiler-cfr-config.usesignatures=Use signatures in addition to descriptors (when they are not obviously incorrect) -service.decompile.decompiler-cfr-config.version=Show the current CFR version -service.decompile.decompiler-procyon-config=Procyon -service.decompile.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Always generate catch block -service.decompile.decompiler-procyon-config.arePreviewFeaturesEnabled=Enable language preview features -service.decompile.decompiler-procyon-config.bytecodeOutputOptions=Bytecode output options -service.decompile.decompiler-procyon-config.disableForEachTransforms=Disable forEach(...) transforms -service.decompile.decompiler-procyon-config.excludeNestedTypes=Exclude nested types -service.decompile.decompiler-procyon-config.flattenSwitchBlocks=Flatten switch blocks -service.decompile.decompiler-procyon-config.forceExplicitImports=Force explicit imports -service.decompile.decompiler-procyon-config.forceExplicitTypeArguments=Force explicit type arguments -service.decompile.decompiler-procyon-config.forceFullyQualifiedReferences=Force fully qualified references -service.decompile.decompiler-procyon-config.forcedCompilerTarget=Target language version -service.decompile.decompiler-procyon-config.includeErrorDiagnostics=Include error diagnostics -service.decompile.decompiler-procyon-config.includeLineNumbersInBytecode=Show debug line numbers (bytecode) -service.decompile.decompiler-procyon-config.isUnicodeOutputEnabled=Enable unicode in output -service.decompile.decompiler-procyon-config.mergeVariables=Merge variables when possible -service.decompile.decompiler-procyon-config.retainPointlessSwitches=Retain useless switches -service.decompile.decompiler-procyon-config.retainRedundantCasts=Retain useless casts -service.decompile.decompiler-procyon-config.showDebugLineNumbers=Show debug line numbers -service.decompile.decompiler-procyon-config.showSyntheticMembers=Show synthetic members -service.decompile.decompiler-procyon-config.simplifyMemberReferences=Simplify member references -service.decompile.decompiler-procyon-config.textBlockLineMinimum=Text block minimum lines -service.decompile.decompiler-vineflower-config=Vineflower -service.decompile.decompiler-vineflower-config.rbr=Remove bridge methods -service.decompile.decompiler-vineflower-config.rsy=Remove synthetic methods and fields -service.decompile.decompiler-vineflower-config.din=Decompile inner classes -service.decompile.decompiler-vineflower-config.dc4=Decompile Java 4 class references -service.decompile.decompiler-vineflower-config.das=Decompile assertions -service.decompile.decompiler-vineflower-config.hes=Hide empty super() -service.decompile.decompiler-vineflower-config.hdc=Hide default constructor -service.decompile.decompiler-vineflower-config.dgs=Decompile generics -service.decompile.decompiler-vineflower-config.ner=No exceptions in return -service.decompile.decompiler-vineflower-config.esm=Ensure synchronized ranges are complete -service.decompile.decompiler-vineflower-config.den=Decompile enums -service.decompile.decompiler-vineflower-config.dpr=Decompile preview features -service.decompile.decompiler-vineflower-config.rgn=Remove reference getClass() -service.decompile.decompiler-vineflower-config.lit=Keep literals as is -service.decompile.decompiler-vineflower-config.bto=Represent boolean as 0/1 -service.decompile.decompiler-vineflower-config.asc=ASCII string characters -service.decompile.decompiler-vineflower-config.nns=Synthetic not set -service.decompile.decompiler-vineflower-config.uto=Treat undefined param type as object -service.decompile.decompiler-vineflower-config.udv=Use LVT names -service.decompile.decompiler-vineflower-config.ump=Use method parameters -service.decompile.decompiler-vineflower-config.rer=Remove empty try-catch blocks -service.decompile.decompiler-vineflower-config.fdi=Decompile finally blocks -service.decompile.decompiler-vineflower-config.inn=Resugar IntelliJ IDEA @NotNull -service.decompile.decompiler-vineflower-config.lac=Decompile lambdas as anonymous classes -service.decompile.decompiler-vineflower-config.bsm=Bytecode to source mapping -service.decompile.decompiler-vineflower-config.dcl=Dump code lines -service.decompile.decompiler-vineflower-config.iib=Ignore invalid bytecode -service.decompile.decompiler-vineflower-config.vac=Verify anonymous classes -service.decompile.decompiler-vineflower-config.tcs=Ternary constant simplification -service.decompile.decompiler-vineflower-config.pam=Pattern matching -service.decompile.decompiler-vineflower-config.tlf=Try-loop fix -service.decompile.decompiler-vineflower-config.tco=Ternary in if conditions (experimental) -service.decompile.decompiler-vineflower-config.swe=Decompile switch expressions -service.decompile.decompiler-vineflower-config.shs=Show hidden statements (debug) -service.decompile.decompiler-vineflower-config.ovr=Override annotation -service.decompile.decompiler-vineflower-config.ssp=Second pass stack simplification -service.decompile.decompiler-vineflower-config.vvm=Verify variable merges (experimental) -service.decompile.decompiler-vineflower-config.iec=Include entire classpath -service.decompile.decompiler-vineflower-config.jrt=Include Java runtime -service.decompile.decompiler-vineflower-config.ega=Explicit generic arguments -service.decompile.decompiler-vineflower-config.isl=Inline simple lambdas -service.decompile.decompiler-vineflower-config.log=Logging level -service.decompile.decompiler-vineflower-config.mpm=Max processing method -service.decompile.decompiler-vineflower-config.ren=Rename entities / members -service.decompile.decompiler-vineflower-config.urc=User renamer class -service.decompile.decompiler-vineflower-config.nls=New line separator -service.decompile.decompiler-vineflower-config.ind=Indent string -service.decompile.decompiler-vineflower-config.pll=Preferred line length -service.decompile.decompiler-vineflower-config.ban=User renamer class -service.decompile.decompiler-vineflower-config.erm=Error message -service.decompile.decompiler-vineflower-config.thr=Thread count -service.decompile.decompiler-vineflower-config.jvn=Use JAD style variable naming -service.decompile.decompiler-vineflower-config.jpr=Use JAD style parameter naming -service.decompile.decompiler-vineflower-config.sef=Skip extra files -service.decompile.decompiler-vineflower-config.win=Warn about inconsistent inner attributes -service.decompile.decompiler-vineflower-config.dbe=Dump bytecode on error -service.decompile.decompiler-vineflower-config.dee=Dump exceptions on error -service.decompile.decompiler-vineflower-config.dec=Decompiler comments -service.decompile.decompiler-vineflower-config.sfc=Source file comments -service.decompile.decompiler-vineflower-config.dcc=Decompile complex constant-dynamic expressions -service.decompile.decompiler-vineflower-config.fji=Force JSR inline service.decompile.decompilers-config=Decompile manager service.decompile.decompilers-config.pref-android-decompiler=Preferred Android decompiler service.decompile.decompilers-config.pref-jvm-decompiler=Preferred Java decompiler +service.decompile.impl=Implementations +service.decompile.impl.decompiler-cfr-config=CFR +service.decompile.impl.decompiler-cfr-config.aexagg=Try to extend and merge exceptions more aggressively +service.decompile.impl.decompiler-cfr-config.aexagg2=Try to extend and merge exceptions more aggressively (may change semantics) +service.decompile.impl.decompiler-cfr-config.aggressivedocopy=Clone code from impossible jumps into loops with 'first' test +service.decompile.impl.decompiler-cfr-config.aggressivedoextension=Fold impossible jumps into do loops with 'first' test +service.decompile.impl.decompiler-cfr-config.aggressiveduff=Fold duff device style switches with additional control. +service.decompile.impl.decompiler-cfr-config.aggressivesizethreshold=Opcode count at which to trigger aggressive reductions +service.decompile.impl.decompiler-cfr-config.allowmalformedswitch=Allow potentially malformed switch statements +service.decompile.impl.decompiler-cfr-config.antiobf=Undo various obfuscations +service.decompile.impl.decompiler-cfr-config.arrayiter=Re-sugar array based iteration +service.decompile.impl.decompiler-cfr-config.collectioniter=Re-sugar collection based iteration +service.decompile.impl.decompiler-cfr-config.commentmonitors=Replace monitors with comments - useful if we're completely confused +service.decompile.impl.decompiler-cfr-config.comments=Output comments describing decompiler status, fallback flags etc. +service.decompile.impl.decompiler-cfr-config.constobf=Undo constant obfuscation +service.decompile.impl.decompiler-cfr-config.decodeenumswitch=Re-sugar switch on enum +service.decompile.impl.decompiler-cfr-config.decodefinally=Re-sugar finally statements +service.decompile.impl.decompiler-cfr-config.decodelambdas=Re-build lambda functions +service.decompile.impl.decompiler-cfr-config.decodestringswitch=Re-sugar switch on String +service.decompile.impl.decompiler-cfr-config.eclipse=Enable transformations to handle Eclipse code better +service.decompile.impl.decompiler-cfr-config.elidescala=Elide things which aren't helpful in scala output (serialVersionUID, @ScalaSignature) +service.decompile.impl.decompiler-cfr-config.forbidanonymousclasses=Don't allow anonymous classes. +service.decompile.impl.decompiler-cfr-config.forbidmethodscopedclasses=Don't allow method scoped classes. +service.decompile.impl.decompiler-cfr-config.forceclassfilever=Force the version of the classfile (and hence java) that classfiles are decompiled as. +service.decompile.impl.decompiler-cfr-config.forcecondpropagate=Pull results of deterministic jumps back through some constant assignments +service.decompile.impl.decompiler-cfr-config.forceexceptionprune=Remove nested exception handlers if they don't change semantics +service.decompile.impl.decompiler-cfr-config.forcereturningifs=Move return up to jump site +service.decompile.impl.decompiler-cfr-config.forcetopsort=Force basic block sorting. Usually only useful when obfuscation is present. +service.decompile.impl.decompiler-cfr-config.forcetopsortaggress=Force extra aggressive topsort options +service.decompile.impl.decompiler-cfr-config.forcetopsortnopull=Force topsort not to pull try blocks +service.decompile.impl.decompiler-cfr-config.forloopaggcapture=Allow for loops to aggressively roll mutations into update section, even if they don't appear to be involved with the predicate +service.decompile.impl.decompiler-cfr-config.hidebridgemethods=Hide bridge methods +service.decompile.impl.decompiler-cfr-config.hidelangimports=Hide imports from java.lang. +service.decompile.impl.decompiler-cfr-config.hidelongstrings=Hide very long strings - useful if obfuscators have placed fake code in strings +service.decompile.impl.decompiler-cfr-config.hideutf=Hide UTF8 characters - quote them instead of showing the raw characters +service.decompile.impl.decompiler-cfr-config.ignoreexceptions=Drop exception information if completely stuck (WARNING : changes semantics, dangerous!) +service.decompile.impl.decompiler-cfr-config.ignoreexceptionsalways=Drop exception information (WARNING : changes semantics, dangerous!) +service.decompile.impl.decompiler-cfr-config.innerclasses=Decompile inner classes +service.decompile.impl.decompiler-cfr-config.instanceofpattern=Re-sugar instanceof pattern matches +service.decompile.impl.decompiler-cfr-config.j14classobj=Reverse java 1.4 class object construction +service.decompile.impl.decompiler-cfr-config.labelledblocks=Allow code to be emitted which uses labelled blocks, (handling odd forward gotos) +service.decompile.impl.decompiler-cfr-config.lenient=Be a bit more lenient in situations where we'd normally throw an exception +service.decompile.impl.decompiler-cfr-config.liftconstructorinit=Lift initialisation code common to all constructors into member initialisation +service.decompile.impl.decompiler-cfr-config.obfattr=Undo attribute obfuscation +service.decompile.impl.decompiler-cfr-config.obfcontrol=Undo control flow obfuscation +service.decompile.impl.decompiler-cfr-config.override=Generate @Override annotations (if method is seen to implement interface method, or override a base class method) +service.decompile.impl.decompiler-cfr-config.previewfeatures=Decompile preview features if class was compiled with 'javac --enable-preview' +service.decompile.impl.decompiler-cfr-config.pullcodecase=Pull code into case statements agressively +service.decompile.impl.decompiler-cfr-config.recordtypes=Re-sugar record types +service.decompile.impl.decompiler-cfr-config.recover=Allow more and more aggressive options to be set if decompilation fails +service.decompile.impl.decompiler-cfr-config.recovertypeclash=Split lifetimes where analysis caused type clash +service.decompile.impl.decompiler-cfr-config.recovertypehints=Recover type hints for iterators from first pass +service.decompile.impl.decompiler-cfr-config.reducecondscope=Reduce the scope of conditionals, possibly generating more anonymous blocks +service.decompile.impl.decompiler-cfr-config.relinkconst=Relink constants - if there is an inlined reference to a field, attempt to de-inline. +service.decompile.impl.decompiler-cfr-config.relinkconststring=Relink constant strings - if there is a local reference to a string which matches a static final, use the static final. +service.decompile.impl.decompiler-cfr-config.removebadgenerics=Hide generics where we've obviously got it wrong, and fallback to non-generic +service.decompile.impl.decompiler-cfr-config.removeboilerplate=Remove boilderplate functions - constructor boilerplate, lambda deserialisation etc. +service.decompile.impl.decompiler-cfr-config.removedeadconditionals=Remove code that can't be executed. +service.decompile.impl.decompiler-cfr-config.removedeadmethods=Remove pointless methods - default constructor etc. +service.decompile.impl.decompiler-cfr-config.removeinnerclasssynthetics=Remove (where possible) implicit outer class references in inner classes +service.decompile.impl.decompiler-cfr-config.renamedupmembers=Rename ambiguous/duplicate fields. +service.decompile.impl.decompiler-cfr-config.renameenumidents=Rename ENUM identifiers which do not match their 'expected' string names. +service.decompile.impl.decompiler-cfr-config.renameillegalidents=Rename identifiers which are not valid java identifiers. +service.decompile.impl.decompiler-cfr-config.renamesmallmembers=Rename small members. Note - this WILL break reflection based access, so is not automatically enabled. +service.decompile.impl.decompiler-cfr-config.sealed=Decompile 'sealed' constructs +service.decompile.impl.decompiler-cfr-config.showinferrable=Decorate methods with explicit types if not implied by arguments +service.decompile.impl.decompiler-cfr-config.showversion=Show used CFR version in header (handy to turn off when regression testing) +service.decompile.impl.decompiler-cfr-config.skipbatchinnerclasses=When processing many files, skip inner classes, as they will be processed as part of outer classes anyway. +service.decompile.impl.decompiler-cfr-config.staticinitreturn=Try to remove return from static init +service.decompile.impl.decompiler-cfr-config.stringbuffer=Convert new StringBuffer().append.append.append to string + string + string +service.decompile.impl.decompiler-cfr-config.stringbuilder=Convert new StringBuilder().append.append.append to string + string + string +service.decompile.impl.decompiler-cfr-config.stringconcat=Convert usages of StringConcatFactor to string + string + string +service.decompile.impl.decompiler-cfr-config.sugarasserts=Re-sugar assert calls +service.decompile.impl.decompiler-cfr-config.sugarboxing=Where possible, remove pointless boxing wrappers +service.decompile.impl.decompiler-cfr-config.sugarenums=Re-sugar enums +service.decompile.impl.decompiler-cfr-config.sugarretrolambda=Where possible, resugar uses of retro lambda +service.decompile.impl.decompiler-cfr-config.switchexpression=Re-sugar switch expression +service.decompile.impl.decompiler-cfr-config.tidymonitors=Remove support code for monitors - e.g. catch blocks just to exit a monitor +service.decompile.impl.decompiler-cfr-config.tryresources=Reconstruct try-with-resources +service.decompile.impl.decompiler-cfr-config.usenametable=Use local variable name table if present +service.decompile.impl.decompiler-cfr-config.usesignatures=Use signatures in addition to descriptors (when they are not obviously incorrect) +service.decompile.impl.decompiler-cfr-config.version=Show the current CFR version +service.decompile.impl.decompiler-procyon-config=Procyon +service.decompile.impl.decompiler-procyon-config.alwaysGenerateExceptionVariableForCatchBlocks=Always generate catch block +service.decompile.impl.decompiler-procyon-config.arePreviewFeaturesEnabled=Enable language preview features +service.decompile.impl.decompiler-procyon-config.bytecodeOutputOptions=Bytecode output options +service.decompile.impl.decompiler-procyon-config.disableForEachTransforms=Disable forEach(...) transforms +service.decompile.impl.decompiler-procyon-config.excludeNestedTypes=Exclude nested types +service.decompile.impl.decompiler-procyon-config.flattenSwitchBlocks=Flatten switch blocks +service.decompile.impl.decompiler-procyon-config.forceExplicitImports=Force explicit imports +service.decompile.impl.decompiler-procyon-config.forceExplicitTypeArguments=Force explicit type arguments +service.decompile.impl.decompiler-procyon-config.forceFullyQualifiedReferences=Force fully qualified references +service.decompile.impl.decompiler-procyon-config.forcedCompilerTarget=Target language version +service.decompile.impl.decompiler-procyon-config.includeErrorDiagnostics=Include error diagnostics +service.decompile.impl.decompiler-procyon-config.includeLineNumbersInBytecode=Show debug line numbers (bytecode) +service.decompile.impl.decompiler-procyon-config.isUnicodeOutputEnabled=Enable unicode in output +service.decompile.impl.decompiler-procyon-config.mergeVariables=Merge variables when possible +service.decompile.impl.decompiler-procyon-config.retainPointlessSwitches=Retain useless switches +service.decompile.impl.decompiler-procyon-config.retainRedundantCasts=Retain useless casts +service.decompile.impl.decompiler-procyon-config.showDebugLineNumbers=Show debug line numbers +service.decompile.impl.decompiler-procyon-config.showSyntheticMembers=Show synthetic members +service.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=Simplify member references +service.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=Text block minimum lines +service.decompile.impl.decompiler-vineflower-config=Vineflower +service.decompile.impl.decompiler-vineflower-config.rbr=Remove bridge methods +service.decompile.impl.decompiler-vineflower-config.rsy=Remove synthetic methods and fields +service.decompile.impl.decompiler-vineflower-config.din=Decompile inner classes +service.decompile.impl.decompiler-vineflower-config.dc4=Decompile Java 4 class references +service.decompile.impl.decompiler-vineflower-config.das=Decompile assertions +service.decompile.impl.decompiler-vineflower-config.hes=Hide empty super() +service.decompile.impl.decompiler-vineflower-config.hdc=Hide default constructor +service.decompile.impl.decompiler-vineflower-config.dgs=Decompile generics +service.decompile.impl.decompiler-vineflower-config.ner=No exceptions in return +service.decompile.impl.decompiler-vineflower-config.esm=Ensure synchronized ranges are complete +service.decompile.impl.decompiler-vineflower-config.den=Decompile enums +service.decompile.impl.decompiler-vineflower-config.dpr=Decompile preview features +service.decompile.impl.decompiler-vineflower-config.rgn=Remove reference getClass() +service.decompile.impl.decompiler-vineflower-config.lit=Keep literals as is +service.decompile.impl.decompiler-vineflower-config.bto=Represent boolean as 0/1 +service.decompile.impl.decompiler-vineflower-config.asc=ASCII string characters +service.decompile.impl.decompiler-vineflower-config.nns=Synthetic not set +service.decompile.impl.decompiler-vineflower-config.uto=Treat undefined param type as object +service.decompile.impl.decompiler-vineflower-config.udv=Use LVT names +service.decompile.impl.decompiler-vineflower-config.ump=Use method parameters +service.decompile.impl.decompiler-vineflower-config.rer=Remove empty try-catch blocks +service.decompile.impl.decompiler-vineflower-config.fdi=Decompile finally blocks +service.decompile.impl.decompiler-vineflower-config.inn=Resugar IntelliJ IDEA @NotNull +service.decompile.impl.decompiler-vineflower-config.lac=Decompile lambdas as anonymous classes +service.decompile.impl.decompiler-vineflower-config.bsm=Bytecode to source mapping +service.decompile.impl.decompiler-vineflower-config.dcl=Dump code lines +service.decompile.impl.decompiler-vineflower-config.iib=Ignore invalid bytecode +service.decompile.impl.decompiler-vineflower-config.vac=Verify anonymous classes +service.decompile.impl.decompiler-vineflower-config.tcs=Ternary constant simplification +service.decompile.impl.decompiler-vineflower-config.pam=Pattern matching +service.decompile.impl.decompiler-vineflower-config.tlf=Try-loop fix +service.decompile.impl.decompiler-vineflower-config.tco=Ternary in if conditions (experimental) +service.decompile.impl.decompiler-vineflower-config.swe=Decompile switch expressions +service.decompile.impl.decompiler-vineflower-config.shs=Show hidden statements (debug) +service.decompile.impl.decompiler-vineflower-config.ovr=Override annotation +service.decompile.impl.decompiler-vineflower-config.ssp=Second pass stack simplification +service.decompile.impl.decompiler-vineflower-config.vvm=Verify variable merges (experimental) +service.decompile.impl.decompiler-vineflower-config.iec=Include entire classpath +service.decompile.impl.decompiler-vineflower-config.jrt=Include Java runtime +service.decompile.impl.decompiler-vineflower-config.ega=Explicit generic arguments +service.decompile.impl.decompiler-vineflower-config.isl=Inline simple lambdas +service.decompile.impl.decompiler-vineflower-config.log=Logging level +service.decompile.impl.decompiler-vineflower-config.mpm=Max processing method +service.decompile.impl.decompiler-vineflower-config.ren=Rename entities / members +service.decompile.impl.decompiler-vineflower-config.urc=User renamer class +service.decompile.impl.decompiler-vineflower-config.nls=New line separator +service.decompile.impl.decompiler-vineflower-config.ind=Indent string +service.decompile.impl.decompiler-vineflower-config.pll=Preferred line length +service.decompile.impl.decompiler-vineflower-config.ban=User renamer class +service.decompile.impl.decompiler-vineflower-config.erm=Error message +service.decompile.impl.decompiler-vineflower-config.thr=Thread count +service.decompile.impl.decompiler-vineflower-config.jvn=Use JAD style variable naming +service.decompile.impl.decompiler-vineflower-config.jpr=Use JAD style parameter naming +service.decompile.impl.decompiler-vineflower-config.sef=Skip extra files +service.decompile.impl.decompiler-vineflower-config.win=Warn about inconsistent inner attributes +service.decompile.impl.decompiler-vineflower-config.dbe=Dump bytecode on error +service.decompile.impl.decompiler-vineflower-config.dee=Dump exceptions on error +service.decompile.impl.decompiler-vineflower-config.dec=Decompiler comments +service.decompile.impl.decompiler-vineflower-config.sfc=Source file comments +service.decompile.impl.decompiler-vineflower-config.dcc=Decompile complex constant-dynamic expressions +service.decompile.impl.decompiler-vineflower-config.fji=Force JSR inline service.io=IO service.io.directories-config=Directories service.io.export-config=Exporting From 5bc18d23259813adf35e90b8b9e07a8400a0354e Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 14 Jan 2024 05:44:43 -0500 Subject: [PATCH 3/4] Add Vineflower to decomp test --- .../services/decompile/DecompileManagerTest.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java b/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java index 1c4496e37..e00a9e3fb 100644 --- a/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java +++ b/recaf-core/src/test/java/software/coley/recaf/services/decompile/DecompileManagerTest.java @@ -1,10 +1,12 @@ package software.coley.recaf.services.decompile; +import jakarta.annotation.Nonnull; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.services.decompile.cfr.CfrDecompiler; import software.coley.recaf.services.decompile.procyon.ProcyonDecompiler; +import software.coley.recaf.services.decompile.vineflower.VineflowerDecompiler; import software.coley.recaf.test.TestBase; import software.coley.recaf.test.TestClassUtils; import software.coley.recaf.test.dummy.HelloWorld; @@ -47,7 +49,14 @@ void testProcyon() { runJvmDecompilation(decompiler); } - private static void runJvmDecompilation(JvmDecompiler decompiler) { + @Test + void testVineflower() { + JvmDecompiler decompiler = decompilerManager.getJvmDecompiler(VineflowerDecompiler.NAME); + assertNotNull(decompiler, "Vineflower decompiler was never registered with manager"); + runJvmDecompilation(decompiler); + } + + private static void runJvmDecompilation(@Nonnull JvmDecompiler decompiler) { try { // Generally, you'd handle results like this, with a when-complete. // The blocking 'get' at the end is just so our test works. From bd8213d88dec005ed4e9ab02989a4541f6d37d31 Mon Sep 17 00:00:00 2001 From: Matt Date: Sun, 14 Jan 2024 06:01:51 -0500 Subject: [PATCH 4/4] Add VF config for logging level, since by default it emits a lot of trace calls which are not desirable --- .../services/decompile/DecompileResult.java | 9 ++++++ .../vineflower/VineflowerConfig.java | 12 ++++++++ .../vineflower/VineflowerDecompiler.java | 3 +- .../vineflower/VineflowerLogger.java | 29 ++++++++++++++----- .../main/resources/translations/en_US.lang | 1 + 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java index 93bca7883..1a26f0027 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/DecompileResult.java @@ -19,6 +19,8 @@ public class DecompileResult { private final int configHash; /** + * Constructor for a successful decompilation. + * * @param text * Decompiled text. * @param configHash @@ -33,6 +35,8 @@ public DecompileResult(@Nonnull String text, int configHash) { } /** + * Constructor for a failed decompilation. + * * @param exception * Failure reason. * @param configHash @@ -47,6 +51,8 @@ public DecompileResult(@Nonnull Throwable exception, int configHash) { } /** + * Constructor for a skipped decompilation. + * * @param configHash * Value of {@link DecompilerConfig#getHash()} of associated decompiler. * Used to determine if cached value in {@link CachedDecompileProperty} is up-to-date with current config. @@ -59,6 +65,9 @@ public DecompileResult(int configHash) { } /** + * Constructor for a skipped decompilation, with pre-defined text. + * Typically used for displaying feedback if the decompiler had an issue or timed out. + * * @param text * Decompiled text. */ diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java index 5ca330600..2b5e572a8 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerConfig.java @@ -5,7 +5,9 @@ import jakarta.inject.Inject; import org.jetbrains.java.decompiler.main.Fernflower; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.slf4j.event.Level; import software.coley.observables.ObservableBoolean; +import software.coley.observables.ObservableObject; import software.coley.recaf.config.BasicConfigValue; import software.coley.recaf.config.ConfigGroups; import software.coley.recaf.services.decompile.BaseDecompilerConfig; @@ -21,6 +23,7 @@ @ApplicationScoped @SuppressWarnings("all") // ignore unused refs / typos public class VineflowerConfig extends BaseDecompilerConfig { + private final ObservableObject loggingLevel = new ObservableObject<>(Level.WARN); private final ObservableBoolean removeBridge = new ObservableBoolean(true); private final ObservableBoolean removeSynthetic = new ObservableBoolean(true); private final ObservableBoolean decompileInner = new ObservableBoolean(true); @@ -74,6 +77,7 @@ public class VineflowerConfig extends BaseDecompilerConfig { @Inject public VineflowerConfig() { super("decompiler-vineflower" + CONFIG_SUFFIX); + addValue(new BasicConfigValue<>("logging-level", Level.class, loggingLevel)); addValue(new BasicConfigValue<>("rbr", boolean.class, removeBridge)); addValue(new BasicConfigValue<>("rsy", boolean.class, removeSynthetic)); addValue(new BasicConfigValue<>("din", boolean.class, decompileInner)); @@ -139,6 +143,14 @@ protected Map getFernflowerProperties() { return properties; } + /** + * @return Level to use for {@link VineflowerLogger}. + */ + @Nonnull + public ObservableObject getLoggingLevel() { + return loggingLevel; + } + @Nonnull public ObservableBoolean getRemoveBridge() { return removeBridge; diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java index d536dc20f..c7d393b1b 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerDecompiler.java @@ -20,7 +20,7 @@ public class VineflowerDecompiler extends AbstractJvmDecompiler { public static final String NAME = "Vineflower"; private final VineflowerConfig config; - private final IFernflowerLogger logger = new VineflowerLogger(); + private final IFernflowerLogger logger; private final IResultSaver dummySaver = new DummyResultSaver(); /** @@ -34,6 +34,7 @@ public VineflowerDecompiler(@Nonnull VineflowerConfig config) { // Change this version to be dynamic when / if the Vineflower authors make a function that returns the version... super(NAME, "1.9.3", config); this.config = config; + logger = new VineflowerLogger(config); } @Nonnull diff --git a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java index d3fef696a..1167f117a 100644 --- a/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java +++ b/recaf-core/src/main/java/software/coley/recaf/services/decompile/vineflower/VineflowerLogger.java @@ -1,7 +1,10 @@ package software.coley.recaf.services.decompile.vineflower; +import jakarta.annotation.Nonnull; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.slf4j.Logger; +import org.slf4j.event.Level; +import software.coley.observables.ObservableObject; import software.coley.recaf.analytics.logging.Logging; /** @@ -11,19 +14,31 @@ */ public class VineflowerLogger extends IFernflowerLogger { private static final Logger logger = Logging.get(VineflowerLogger.class); + private static final String VF_PREFIX = "VF: "; + private final ObservableObject level; + + public VineflowerLogger(@Nonnull VineflowerConfig config) { + this.level = config.getLoggingLevel(); + } @Override - public void writeMessage(String s, Severity severity) { + public void writeMessage(String message, Severity severity) { switch (severity) { - case TRACE -> logger.trace(s); - case INFO -> logger.info(s); - case WARN -> logger.warn(s); - case ERROR -> logger.error(s); + case TRACE -> { + if (level.getValue().compareTo(Level.TRACE) >= 0) logger.trace(VF_PREFIX + message); + } + case INFO -> { + if (level.getValue().compareTo(Level.INFO) >= 0) logger.info(VF_PREFIX + message); + } + case WARN -> { + if (level.getValue().compareTo(Level.WARN) >= 0) logger.warn(VF_PREFIX + message); + } + case ERROR -> logger.error(VF_PREFIX + message); } } @Override - public void writeMessage(String s, Severity severity, Throwable throwable) { - writeMessage(s, severity); + public void writeMessage(String message, Severity severity, Throwable throwable) { + logger.error(VF_PREFIX + message, throwable); } } diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index 07ba3a05b..435fa9c2d 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -558,6 +558,7 @@ service.decompile.impl.decompiler-procyon-config.showSyntheticMembers=Show synth service.decompile.impl.decompiler-procyon-config.simplifyMemberReferences=Simplify member references service.decompile.impl.decompiler-procyon-config.textBlockLineMinimum=Text block minimum lines service.decompile.impl.decompiler-vineflower-config=Vineflower +service.decompile.impl.decompiler-vineflower-config.logging-level=Logging level service.decompile.impl.decompiler-vineflower-config.rbr=Remove bridge methods service.decompile.impl.decompiler-vineflower-config.rsy=Remove synthetic methods and fields service.decompile.impl.decompiler-vineflower-config.din=Decompile inner classes