diff --git a/docgen/gradle.properties b/docgen/gradle.properties index 924d5fd..6a72dee 100644 --- a/docgen/gradle.properties +++ b/docgen/gradle.properties @@ -1 +1 @@ -vineflowerVersion=1.9.2 +vineflowerVersion=1.11.0-SNAPSHOT diff --git a/docgen/src/main/java/org/vineflower/docgen/UsageGenerator.java b/docgen/src/main/java/org/vineflower/docgen/UsageGenerator.java index 46f1a18..cc5a528 100644 --- a/docgen/src/main/java/org/vineflower/docgen/UsageGenerator.java +++ b/docgen/src/main/java/org/vineflower/docgen/UsageGenerator.java @@ -41,101 +41,33 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.stream.Collectors; + +import io.pebbletemplates.pebble.utils.Pair; +import org.jetbrains.java.decompiler.api.DecompilerOption; +import org.jetbrains.java.decompiler.api.plugin.Plugin; +import org.jetbrains.java.decompiler.main.Init; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.slf4j.Logger; import org.vineflower.docgen.util.Logging; public class UsageGenerator { - private static final Logger LOGGER = Logging.logger(); - private static final int PSF_MASK = Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL; - private static final String ANNOTATION_PREFIX = "org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences$"; - private static final boolean HAS_LEGACY_SHORT_NAME; - - static { - boolean hasShortName = false; - try { - Class.forName(ANNOTATION_PREFIX + "ShortName"); - hasShortName = true; - } catch (final ClassNotFoundException ignored) { - } - HAS_LEGACY_SHORT_NAME = hasShortName; - } - - record Problem(String argumentName, String message) {} - - record ArgumentInfo(String argumentName, String friendlyName, String legacyShortName, String description, String defaultValue, String type) { - static ArgumentInfo fromField(final Field field, final Consumer problemConsumer) { - final String argumentName; - try { - argumentName = (String) field.get(null); - } catch (IllegalAccessException e) { - problemConsumer.accept(new Problem(field.getName(), "could not read field value")); - return null; - } - - String friendlyName = annotationValueOrNull(field,"Name"); - if (friendlyName == null) { - problemConsumer.accept(new Problem(argumentName, "missing @Name annotation")); - } - String description = annotationValueOrNull(field,"Description"); - if (description == null) { - problemConsumer.accept(new Problem(argumentName, "missing @Description annotation")); - } - String defaultValue = valueOfOrNull(IFernflowerPreferences.getDefaults().get(argumentName)); - // 1.10+ - String legacyShortName = annotationValueOrNull(field, "ShortName"); - String type = annotationValueOrNull(field, "Type"); - - return new ArgumentInfo(argumentName, friendlyName, legacyShortName, description, defaultValue, type); - } - } - - public static boolean generate(final Path usageFile, final PebbleEngine engine) throws IOException { - final List problems = new ArrayList<>(); - final List info = Arrays.stream(IFernflowerPreferences.class.getFields()) - .filter(UsageGenerator::isPublicStaticFinalString) - .filter(field -> field.isAnnotationPresent(IFernflowerPreferences.Name.class)) // basic sanity check - .map(f -> ArgumentInfo.fromField(f, problems::add)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - - info.sort(Comparator.comparing(ArgumentInfo::argumentName)); + Init.init(); - if (!problems.isEmpty()) { - LOGGER.error("There were problems discovering information about argument usage:"); - for (final var problem : problems) { - LOGGER.error("- '{}': {}", problem.argumentName(), problem.message()); - } - return false; - } + List>> options = DecompilerOption.getAllByPlugin() + .entrySet().stream() + .sorted(Comparator.comparing(entry -> entry.getKey() != null ? entry.getKey().id() : "")) + .map(entry -> { + String pluginName = entry.getKey() != null ? "Plugin: " + entry.getKey().id() : null; + List pluginOptions = entry.getValue(); + return new Pair<>(pluginName, pluginOptions); + }) + .toList(); final PebbleTemplate tpl = engine.getTemplate("usage.peb"); try (final Writer writer = Files.newBufferedWriter(usageFile, StandardCharsets.UTF_8)) { - tpl.evaluate(writer, Map.of("args", info, "hasLegacyShortNames", HAS_LEGACY_SHORT_NAME)); + tpl.evaluate(writer, Map.of("groups", options)); } return true; } - - private static boolean isPublicStaticFinalString(final Field field) { - return String.class.equals(field.getType()) && (field.getModifiers() & PSF_MASK) == PSF_MASK; - } - - private static String valueOfOrNull(final Object input) { - return input == null ? null : String.valueOf(input); - } - - private static String annotationValueOrNull(final AnnotatedElement element, final String annotation) { - final String qualifiedName = ANNOTATION_PREFIX + annotation; - for (final Annotation a : element.getAnnotations()) { - if (a.annotationType().getName().equals(qualifiedName)) { - try { - return (String) a.getClass().getMethod("value").invoke(a); - } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException("Failed to read value field from annotation " + a.annotationType().getName() + " on " + element, e); - } - } - } - return null; - } } diff --git a/docgen/src/main/resources/tmpl/usage.peb b/docgen/src/main/resources/tmpl/usage.peb index f4ce0d3..d71907d 100644 --- a/docgen/src/main/resources/tmpl/usage.peb +++ b/docgen/src/main/resources/tmpl/usage.peb @@ -1,19 +1,25 @@ -{# @pebvariable name="args" type="java.util.List" #} -{# @pebvariable name="hasLegacyShortNames" type="boolean" #} -{% for arg in args %} -```{option} {% if hasLegacyShortNames %} ---{{ arg.argumentName }}{% if arg.type is not null %}={{ "{" }}{{ arg.type }}{{ "}" }}{% endif %}, {%if arg.legacyShortName is not null %}-{{ arg.legacyShortName }}{% if arg.type is not null %}={{ "{" }}{{ arg.type }}{{ "}" }}{% endif %}{% endif %} +{# @pebvariable name="groups" type="java.util.List>>" #} +{% for group in groups %} + +{% if group.left is not null %} +--- +### {{ group.left }} {% else %} --{{ arg.argumentName }}={{ "{" }}value{{ "}" }} +### Base Decompiler Options {% endif %} -{{ arg.description }} +{% for option in group.right %} +```{option} --{{ option.id }}={{ option.type }} -{%if arg.defaultValue %} +{# Escape $, MathJax uses it #} +{{ option.description | replace({'$': '\$'}) }} -**Default**: `{{ arg.defaultValue }}` +{% if option.defaultValue %} +**Default**: `{{ option.defaultValue }}` {% endif %} ``` {% endfor %} + +{% endfor %} diff --git a/source/generated/usage.md b/source/generated/usage.md deleted file mode 100644 index e703710..0000000 --- a/source/generated/usage.md +++ /dev/null @@ -1,421 +0,0 @@ -```{option} --ascii-strings={bool}, -asc={bool} -Encode non-ASCII characters in string and character literals as Unicode escapes. - -**Default**: `0` - -``` - -```{option} --banner={string}, -ban={string} -A message to display at the top of the decompiled file. - -``` - -```{option} --boolean-as-int={bool}, -bto={bool} -Represent integers 0 and 1 as booleans. - -**Default**: `1` - -``` - -```{option} --bytecode-source-mapping={bool}, -bsm={bool} -Map Bytecode to source lines. - -**Default**: `0` - -``` - -```{option} --decompile-assert={bool}, -das={bool} -Decompile assert statements. - -**Default**: `1` - -``` - -```{option} --decompile-complex-constant-dynamic={bool}, -dcc={bool} -Some constant-dynamic expressions can't be converted to a single Java expression with identical run-time behaviour. This decompiles them to a similar non-lazy expression, marked with a comment. - -**Default**: `0` - -``` - -```{option} --decompile-enums={bool}, -den={bool} -Decompile enums. - -**Default**: `1` - -``` - -```{option} --decompile-finally={bool}, -fdi={bool} -Decompile finally blocks. - -**Default**: `1` - -``` - -```{option} --decompile-generics={bool}, -dgs={bool} -Decompile generics in classes, methods, fields, and variables. - -**Default**: `1` - -``` - -```{option} --decompile-inner={bool}, -din={bool} -Process inner classes and add them to the decompiled output. - -**Default**: `1` - -``` - -```{option} --decompile-java4={bool}, -dc4={bool} -Resugar the Java 1-4 class reference format instead of leaving the synthetic code. - -**Default**: `1` - -``` - -```{option} --decompile-preview={bool}, -dpr={bool} -Decompile features marked as preview or incubating in the latest Java versions. - -**Default**: `1` - -``` - -```{option} --decompile-switch-expressions={bool}, -swe={bool} -Decompile switch expressions in modern Java class files. - -**Default**: `1` - -``` - -```{option} --decompiler-comments={bool}, -dec={bool} -Sometimes, odd behavior of the bytecode or unfixable problems occur. This enables or disables the adding of those to the decompiled output. - -**Default**: `1` - -``` - -```{option} --dump-bytecode-on-error={bool}, -dbe={bool} -Put the bytecode in the method body when an error occurs. - -**Default**: `1` - -``` - -```{option} --dump-code-lines={bool}, -dcl={bool} -Dump line mappings to output archive zip entry extra data. - -**Default**: `0` - -``` - -```{option} --dump-exception-on-error={bool}, -dee={bool} -Put the exception message in the method body or source file when an error occurs. - -**Default**: `1` - -``` - -```{option} --dump-text-tokens={bool}, -dtt={bool} -Dump Text Tokens on each class file - -**Default**: `0` - -``` - -```{option} --ensure-synchronized-monitors={bool}, -esm={bool} -If a synchronized block has a monitorenter without any corresponding monitorexit, try to deduce where one should be to ensure the synchronized is correctly decompiled. - -**Default**: `1` - -``` - -```{option} --error-message={string}, -erm={string} -Message to display when an error occurs in the decompiler. - -**Default**: `Please report this to the Vineflower issue tracker, at https://github.com/Vineflower/vineflower/issues with a copy of the class file (if you have the rights to distribute it!)` - -``` - -```{option} --explicit-generics={bool}, -ega={bool} -Put explicit diamond generic arguments on method calls. - -**Default**: `0` - -``` - -```{option} --force-jsr-inline={bool}, -fji={bool} -Forces the processing of JSR instructions even if the class files shouldn't contain it (Java 7+) - -**Default**: `0` - -``` - -```{option} --hide-default-constructor={bool}, -hdc={bool} -Hide constructors with no parameters and no code. - -**Default**: `1` - -``` - -```{option} --hide-empty-super={bool}, -hes={bool} -Hide super() calls with no parameters. - -**Default**: `1` - -``` - -```{option} --ignore-invalid-bytecode={bool}, -iib={bool} -Ignore bytecode that is malformed. - -**Default**: `0` - -``` - -```{option} --include-classpath={bool}, -iec={bool} -Give the decompiler information about every jar on the classpath. - -**Default**: `0` - -``` - -```{option} --include-runtime={string}, -jrt={string} -Give the decompiler information about the Java runtime, either 1 or current for the current runtime, or a path to another runtime - -``` - -```{option} --incorporate-returns={bool}, -ner={bool} -Integrate returns better in try-catch blocks instead of storing them in a temporary variable. - -**Default**: `1` - -``` - -```{option} --indent-string={string}, -ind={string} -A string of spaces or tabs that is placed for each indent level. - -**Default**: ` ` - -``` - -```{option} --inline-simple-lambdas={bool}, -isl={bool} -Remove braces on simple, one line, lambda expressions. - -**Default**: `1` - -``` - -```{option} --keep-literals={bool}, -lit={bool} -Keep NaN, infinities, and pi values as is without resugaring them. - -**Default**: `0` - -``` - -```{option} --lambda-to-anonymous-class={bool}, -lac={bool} -Decompile lambda expressions as anonymous classes. - -**Default**: `0` - -``` - -```{option} --log-level={string}, -log={string} -Logging level. Must be one of: 'info', 'debug', 'warn', 'error'. - -**Default**: `INFO` - -``` - -```{option} --mark-corresponding-synthetics, -mcs -Mark lambdas and anonymous and local classes with their respective synthetic constructs - -**Default**: `0` - -``` - -```{option} --max-time-per-method={int}, -mpm={int} -Maximum time in seconds to process a method. This is deprecated, do not use. - -**Default**: `0` - -``` - -```{option} --new-line-separator={bool}, -nls={bool} -Use \n instead of \r\n for new lines. Deprecated, do not use. - -**Default**: `1` - -``` - -```{option} --override-annotation={bool}, -ovr={bool} -Display override annotations for methods known to the decompiler. - -**Default**: `1` - -``` - -```{option} --pattern-matching={bool}, -pam={bool} -Decompile with if and switch pattern matching enabled. - -**Default**: `1` - -``` - -```{option} --preferred-line-length={int}, -pll={int} -Max line length before formatting is applied. - -**Default**: `160` - -``` - -```{option} --remove-bridge={bool}, -rbr={bool} -Removes any methods that are marked as bridge from the decompiled output. - -**Default**: `1` - -``` - -```{option} --remove-empty-try-catch={bool}, -rer={bool} -Remove try-catch blocks with no code. - -**Default**: `1` - -``` - -```{option} --remove-getclass={bool}, -rgn={bool} -Remove synthetic getClass() calls created by code such as 'obj.new Inner()'. - -**Default**: `1` - -``` - -```{option} --remove-imports={bool}, -rim={bool} -Remove import statements from the decompiled code - -**Default**: `0` - -``` - -```{option} --remove-synthetic={bool}, -rsy={bool} -Removes any methods and fields that are marked as synthetic from the decompiled output. - -**Default**: `1` - -``` - -```{option} --rename-members={bool}, -ren={bool} -Rename classes, fields, and methods with a number suffix to help in deobfuscation. - -**Default**: `0` - -``` - -```{option} --show-hidden-statements={bool}, -shs={bool} -Display hidden code blocks for debugging purposes. - -**Default**: `0` - -``` - -```{option} --simplify-stack={bool}, -ssp={bool} -Simplify variables across stack bounds to resugar complex statements. - -**Default**: `1` - -``` - -```{option} --skip-extra-files={bool}, -sef={bool} -Skip copying non-class files from the input folder or file to the output - -**Default**: `0` - -``` - -```{option} --sourcefile-comments={bool}, -sfc={bool} -Add debug comments showing the class SourceFile attribute if present. - -**Default**: `0` - -``` - -```{option} --synthetic-not-set={bool}, -nns={bool} -Treat some known structures as synthetic even when not explicitly set. - -**Default**: `0` - -``` - -```{option} --ternary-constant-simplification={bool}, -tcs={bool} -Fold branches of ternary expressions that have boolean true and false constants. - -**Default**: `0` - -``` - -```{option} --ternary-in-if={bool}, -tco={bool} -Tries to collapse if statements that have a ternary in their condition. - -**Default**: `1` - -``` - -```{option} --thread-count={int}, -thr={int} -How many threads to use to decompile. - -**Default**: `4` - -``` - -```{option} --try-loop-fix={bool}, -tlf={bool} -Fixes rare cases of malformed decompilation when try blocks are found inside of while loops - -**Default**: `1` - -``` - -```{option} --undefined-as-object={bool}, -uto={bool} -Treat nameless types as java.lang.Object. - -**Default**: `1` - -``` - -```{option} --use-lvt-names={bool}, -udv={bool} -Use LVT names for local variables and parameters instead of var<index>_<version>. - -**Default**: `1` - -``` - -```{option} --use-method-parameters={bool}, -ump={bool} -Use method parameter names, as given in the MethodParameters attribute. - -**Default**: `1` - -``` - -```{option} --user-renamer-class={string}, -urc={string} -Path to a class that implements IIdentifierRenamer. - -``` - -```{option} --verify-anonymous-classes={bool}, -vac={bool} -Verify that anonymous classes are local. - -**Default**: `0` - -``` - -```{option} --verify-merges={bool}, -vvm={bool} -Tries harder to verify the validity of variable merges. If there are strange variable recompilation issues, this is a good place to start. - -**Default**: `0` - -``` - -```{option} --warn-inconsistent-inner-attributes={bool}, -win={bool} -Warn about inconsistent inner class attributes - -**Default**: `1` - -``` - diff --git a/source/usage.md b/source/usage.md index 55e3690..fa2d6b7 100644 --- a/source/usage.md +++ b/source/usage.md @@ -40,7 +40,7 @@ It is possible to limit decompiled output to only a certain subset of the input Only members whose paths start with `prefix` will be emitted ``` -% The rest can be automatically inferred from IFernflowerPreferences +% The rest are collected by Vineflower's DecompilerOption record and its getAllByPlugin method The following values change the decompilation behavior itself.