From 88d3d7481fb0d3f908ef19a220453ffbceeffc62 Mon Sep 17 00:00:00 2001 From: md_5 Date: Thu, 6 Dec 2018 10:00:00 +1100 Subject: Clean up old decompiler changes + add new sugaring Contains code from: Alexandru-Constantin Bledea - Boxing Dmitry Cherniachenko - Vararg parameters, Boxing Egor Ushakov - Lambda, String Quoting Lex Manos - Switch decompilation Thinkofdeath - Formatting --- .../java/decompiler/main/ClassWriter.java | 65 ++++++-- .../jetbrains/java/decompiler/main/Fernflower.java | 2 +- .../java/decompiler/main/InitializerProcessor.java | 2 +- .../main/collectors/ImportCollector.java | 2 +- .../main/collectors/VarNamesCollector.java | 3 +- .../main/extern/IFernflowerPreferences.java | 2 +- .../java/decompiler/main/rels/ClassWrapper.java | 2 +- .../java/decompiler/main/rels/LambdaProcessor.java | 26 ++- .../decompiler/main/rels/NestedClassProcessor.java | 156 ++++++++++++++++-- .../modules/decompiler/ExprProcessor.java | 43 ++++- .../modules/decompiler/exps/ConstExprent.java | 29 +++- .../modules/decompiler/exps/FieldExprent.java | 2 +- .../modules/decompiler/exps/InvocationExprent.java | 182 ++++++++++++++++----- .../modules/decompiler/exps/NewExprent.java | 30 ++++ .../modules/decompiler/exps/VarExprent.java | 5 +- .../decompiler/stats/CatchAllStatement.java | 6 +- .../modules/decompiler/stats/CatchStatement.java | 6 +- .../modules/decompiler/stats/DoStatement.java | 6 +- .../modules/decompiler/stats/IfStatement.java | 39 ++++- .../modules/decompiler/stats/SwitchStatement.java | 86 +++++++++- .../decompiler/stats/SynchronizedStatement.java | 24 ++- .../modules/decompiler/vars/VarProcessor.java | 15 +- .../modules/decompiler/vars/VarVersionPaar.java | 10 +- .../java/decompiler/struct/StructClass.java | 3 + .../java/decompiler/struct/gen/VarType.java | 2 + src/org/jetbrains/java/decompiler/util/Util.java | 12 ++ .../jetbrains/java/decompiler/util/VarHelper.java | 104 ++++++++++++ 27 files changed, 747 insertions(+), 117 deletions(-) create mode 100644 src/org/jetbrains/java/decompiler/util/Util.java create mode 100644 src/org/jetbrains/java/decompiler/util/VarHelper.java (limited to 'src/org/jetbrains/java/decompiler') diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java index 2b61dc2..3aa552f 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -42,6 +42,7 @@ import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.struct.gen.generics.*; import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.Util; import java.util.LinkedHashMap; import java.util.List; @@ -110,7 +111,7 @@ public class ClassWriter { } buffer.append("::"); - buffer.append(node.lambda_information.content_method_name); + buffer.append("".equals(node.lambda_information.content_method_name) ? "new" : node.lambda_information.content_method_name); } else { // lambda method @@ -132,7 +133,13 @@ public class ClassWriter { buffer.append(", "); } - String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + String typeName = ExprProcessor.getCastTypeName(md_content.params[i].copy()); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) + && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0, typeName, false)); buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors firstParameter = false; @@ -179,7 +186,17 @@ public class ClassWriter { // write class definition int start_class_def = buffer.length(); - writeClassDefinition(node, buffer, indent); + boolean empty = cl.getFields().isEmpty() && cl.getMethods().isEmpty(); + if (cl.hasModifier(CodeConstants.ACC_ENUM)) { + empty = true; + for (StructField fd : cl.getFields()) { + if (fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + empty = false; + break; + } + } + } + writeClassDefinition(node, buffer, indent, empty); // // count lines in class definition the easiest way // total_offset_lines = buffer.substring(start_class_def).toString().split(lineSeparator, -1).length - 1; @@ -277,12 +294,12 @@ public class ClassWriter { DecompilerContext.getLogger().endWriteClass(); } - private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent) { + private void writeClassDefinition(ClassNode node, TextBuffer buffer, int indent, boolean empty) { String lineSeparator = DecompilerContext.getNewLineSeparator(); if (node.type == ClassNode.CLASS_ANONYMOUS) { buffer.append(" {"); - buffer.append(lineSeparator); + if (!empty) buffer.append(lineSeparator); return; } @@ -384,7 +401,10 @@ public class ClassWriter { } buffer.append('{'); - buffer.append(lineSeparator); + if (!empty) { + buffer.append(lineSeparator); + buffer.append(lineSeparator); // Spigot + } } private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, TextBuffer buffer, int indent, BytecodeMappingTracer tracer) { @@ -514,7 +534,7 @@ public class ClassWriter { buffer.append(typeName); buffer.append(" "); - String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0, typeName, false)); // Spigot buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors firstParameter = false; @@ -701,6 +721,10 @@ public class ClassWriter { int start = isEnum && init && descriptor == null ? 2 : 0; int params = descriptor == null ? md.params.length : descriptor.params.size(); for (int i = start; i < params; i++) { + // Spigot Start + String typeName; + boolean isVarArg; + // Spigot end if (signFields == null || signFields.get(i) == null) { if (!firstParameter) { buffer.append(", "); @@ -715,12 +739,12 @@ public class ClassWriter { if (descriptor != null) { GenericType parameterType = descriptor.params.get(i); - boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); + isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); // Spigot if (isVarArg) { parameterType.arraydim--; } - String typeName = GenericMain.getGenericCastTypeName(parameterType); + typeName = GenericMain.getGenericCastTypeName(parameterType); // Spigot if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); @@ -735,12 +759,12 @@ public class ClassWriter { else { VarType parameterType = md.params[i].copy(); - boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); + isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); // Spigot if (isVarArg) { parameterType.decArrayDim(); } - String typeName = ExprProcessor.getCastTypeName(parameterType); + typeName = ExprProcessor.getCastTypeName(parameterType); // Spigot if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); @@ -754,7 +778,7 @@ public class ClassWriter { } buffer.append(' '); - String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0, typeName, isVarArg)); // Spigot buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors firstParameter = false; @@ -806,9 +830,10 @@ public class ClassWriter { //TODO: for now only start line set buffer.setCurrentLine(startLine); - buffer.append('{').appendLineSeparator(); + buffer.append('{'); RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + boolean empty = false; if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence try { @@ -816,8 +841,15 @@ public class ClassWriter { String code = root.toJava(indent + 1, tracer); + if (Util.rtrim(code).isEmpty()) code = ""; + hideMethod = (clinit || dinit || hideConstructor(wrapper, init, throwsExceptions, paramCount)) && code.length() == 0; + if (!code.isEmpty()) { + buffer.appendLineSeparator(); + } else { + empty = true; + } buffer.append(code); } catch (Throwable ex) { @@ -832,7 +864,10 @@ public class ClassWriter { buffer.append(lineSeparator); } - buffer.appendIndent(indent).append('}').appendLineSeparator(); + if (!empty) { + buffer.appendIndent(indent); + } + buffer.append('}').appendLineSeparator(); } } finally { @@ -918,7 +953,7 @@ public class ClassWriter { } private static void appendComment(TextBuffer buffer, String comment, int indent, String lineSeparator) { - buffer.appendIndent(indent).append("// $FF: ").append(comment).append(lineSeparator); + // buffer.appendIndent(indent).append("// $FF: ").append(comment).append(lineSeparator); // Spigot: Squash comments } private static final String[] ANNOTATION_ATTRIBUTES = { diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java index 15baac5..85d9b00 100644 --- a/src/org/jetbrains/java/decompiler/main/Fernflower.java +++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -85,7 +85,7 @@ public class Fernflower implements IDecompiledData { TextBuffer buffer = new TextBuffer(ClassesProcessor.AVERAGE_CLASS_SIZE); buffer.append(DecompilerContext.getProperty(IFernflowerPreferences.BANNER).toString()); classesProcessor.writeClass(cl, buffer); - return buffer.toString(); + return org.spigotmc.fernflower.EclipseFormatter.format(buffer.toString()); // Spigot } catch (Throwable ex) { DecompilerContext.getLogger().writeMessage("Class " + cl.qualifiedName + " couldn't be fully decompiled.", ex); diff --git a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java index aabea15..4c3f625 100644 --- a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -139,7 +139,7 @@ public class InitializerProcessor { if (fexpr.isStatic() && fexpr.getClassname().equals(cl.qualifiedName) && cl.hasField(fexpr.getName(), fexpr.getDescriptor().descriptorString)) { - if (isExprentIndependent(asexpr.getRight(), meth)) { + if (true || isExprentIndependent(asexpr.getRight(), meth)) { // Spigot String keyField = InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString); if (!wrapper.getStaticFieldInitializers().containsKey(keyField)) { diff --git a/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java index c66a3e0..fae600a 100644 --- a/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java +++ b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java @@ -145,7 +145,7 @@ public class ImportCollector { // exclude a current class or one of the nested ones, java.lang and empty packages if (!setNotImportedNames.contains(ent.getKey()) && !JAVA_LANG_PACKAGE.equals(ent.getValue()) && - !ent.getValue().isEmpty()) { + !ent.getValue().isEmpty() && !ent.getValue().equals(this.currentPackagePoint)) { // Spigot: Remove same package imports res.add(ent.getValue() + "." + ent.getKey()); } } diff --git a/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java b/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java index dfde971..8d9d5c0 100644 --- a/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java +++ b/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java @@ -15,6 +15,7 @@ */ package org.jetbrains.java.decompiler.main.collectors; +import java.util.Collection; import java.util.HashSet; public class VarNamesCollector { @@ -24,7 +25,7 @@ public class VarNamesCollector { public VarNamesCollector() { } - public VarNamesCollector(HashSet setNames) { + public VarNamesCollector(Collection setNames) { usedNames.addAll(setNames); } diff --git a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java index 677d05d..7d96e62 100644 --- a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java +++ b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java @@ -91,6 +91,6 @@ public interface IFernflowerPreferences { put(MAX_PROCESSING_METHOD, "0"); put(RENAME_ENTITIES, "0"); put(NEW_LINE_SEPARATOR, (InterpreterUtil.IS_WINDOWS ? "0" : "1")); - put(INDENT_STRING, " "); + put(INDENT_STRING, " "); }}); } diff --git a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java index 4a32179..b89bcd2 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java +++ b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java @@ -131,7 +131,7 @@ public class ClassWrapper { int varindex = 0; for (int i = 0; i < paramcount; i++) { - varproc.setVarName(new VarVersionPaar(varindex, 0), vc.getFreeName(varindex)); + varproc.setVarName(new VarVersionPaar(varindex, 0, classStruct.qualifiedName, false), vc.getFreeName(varindex)); if (thisvar) { if (i == 0) { diff --git a/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java index 743ce12..437568b 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java @@ -36,10 +36,9 @@ import java.util.*; public class LambdaProcessor { - private static final String JAVAC_LAMBDA_CLASS = "java/lang/invoke/LambdaMetafactory"; - private static final String JAVAC_LAMBDA_METHOD = "metafactory"; - private static final String JAVAC_LAMBDA_METHOD_DESCRIPTOR = - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"; + @SuppressWarnings("SpellCheckingInspection") private static final String JAVAC_LAMBDA_CLASS = "java/lang/invoke/LambdaMetafactory"; + @SuppressWarnings("SpellCheckingInspection") private static final String JAVAC_LAMBDA_METHOD = "metafactory"; + @SuppressWarnings("SpellCheckingInspection") private static final String JAVAC_LAMBDA_ALT_METHOD = "altMetafactory"; public void processClass(ClassNode node) throws IOException { @@ -47,9 +46,7 @@ public class LambdaProcessor { processClass(child); } - if (node.nested.isEmpty()) { - hasLambda(node); - } + hasLambda(node); } public boolean hasLambda(ClassNode node) throws IOException { @@ -67,17 +64,16 @@ public class LambdaProcessor { return false; // no bootstrap constants in pool } - Set lambda_methods = new HashSet(); + BitSet lambda_methods = new BitSet(); // find lambda bootstrap constants for (int i = 0; i < bootstrap.getMethodsNumber(); ++i) { LinkConstant method_ref = bootstrap.getMethodReference(i); // method handle + // FIXME: extend for Eclipse etc. at some point if (JAVAC_LAMBDA_CLASS.equals(method_ref.classname) && - JAVAC_LAMBDA_METHOD.equals(method_ref.elementname) && - JAVAC_LAMBDA_METHOD_DESCRIPTOR - .equals(method_ref.descriptor)) { // check for javac lambda structure. FIXME: extend for Eclipse etc. at some point - lambda_methods.add(i); + (JAVAC_LAMBDA_METHOD.equals(method_ref.elementname) || JAVAC_LAMBDA_ALT_METHOD.equals(method_ref.elementname))) { + lambda_methods.set(i); } } @@ -101,7 +97,7 @@ public class LambdaProcessor { if (instr.opcode == CodeConstants.opc_invokedynamic) { LinkConstant invoke_dynamic = cl.getPool().getLinkConstant(instr.getOperand(0)); - if (lambda_methods.contains(invoke_dynamic.index1)) { // lambda invocation found + if (lambda_methods.get(invoke_dynamic.index1)) { // lambda invocation found List bootstrap_arguments = bootstrap.getMethodArguments(invoke_dynamic.index1); MethodDescriptor md = MethodDescriptor.parseDescriptor(invoke_dynamic.descriptor); @@ -122,7 +118,9 @@ public class LambdaProcessor { node_lambda.parent = node; clprocessor.getMapRootClasses().put(node_lambda.simpleName, node_lambda); - mapMethodsLambda.put(node_lambda.lambda_information.content_method_key, node_lambda.simpleName); + if (!node_lambda.lambda_information.is_method_reference) { + mapMethodsLambda.put(node_lambda.lambda_information.content_method_key, node_lambda.simpleName); + } } } } diff --git a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java index 25ff812..23ad443 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java +++ b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java @@ -16,6 +16,7 @@ package org.jetbrains.java.decompiler.main.rels; import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.cfg.BasicBlock; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; @@ -25,8 +26,11 @@ import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectNode; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.CatchStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.DoStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.SequenceStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; @@ -40,6 +44,7 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil; import java.util.*; import java.util.Map.Entry; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; public class NestedClassProcessor { @@ -58,6 +63,10 @@ public class NestedClassProcessor { return; } + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + gatherEnumSwitchMaps(node); + } + if (node.type != ClassNode.CLASS_LAMBDA) { computeLocalVarsAndDefinitions(node); @@ -102,9 +111,131 @@ public class NestedClassProcessor { } } + /** + * When Java introduced Enums they aded the ability to use them in Switch statements. + * This was done in a purely syntax sugar way using the old switch on int methods. + * The compiler creates a synthetic class with a static int array field. + * To support enums changing post compile, It initializes this field with a length of the current enum length. + * And then for every referenced enum value it adds a mapping in the form of: + * try { + * field[Enum.VALUE.ordinal()] = 1; + * } catch (FieldNotFoundException e) {} + * + * If a class has multiple switches on multiple enums, the compiler adds the init and try list to the BEGINNING of the static initalizer. + * But they add the field to the END of the fields list. + */ + private void gatherEnumSwitchMaps(ClassNode node) { + if (node.wrapper == null) { + return; + } + for (ClassNode child : node.nested) { + gatherEnumSwitchMaps(child); + } + + MethodWrapper clinit = node.wrapper.getMethodWrapper("", "()V"); + if (clinit == null || clinit.root == null || clinit.root.getFirst().type != Statement.TYPE_SEQUENCE) { + return; + } + + final int STATIC_FINAL_SYNTHETIC = CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_SYNTHETIC; + Set potentialFields = new HashSet(); + for (StructField fd : node.classStruct.getFields()) { + if ((fd.getAccessFlags() & STATIC_FINAL_SYNTHETIC) == STATIC_FINAL_SYNTHETIC && "[I".equals(fd.getDescriptor())) { + potentialFields.add(fd.getName()); + } + } + + if (potentialFields.size() == 0) { + return; + } + + SequenceStatement seq = (SequenceStatement)clinit.root.getFirst(); + for (int x = 0; x < seq.getStats().size();) { + Statement stat = seq.getStats().get(x); + if (stat.type != Statement.TYPE_BASICBLOCK || stat.getExprents() == null || stat.getExprents().size() != 1 || stat.getExprents().get(0).type != Exprent.EXPRENT_ASSIGNMENT) { + break; + } + AssignmentExprent ass = (AssignmentExprent)stat.getExprents().get(0); + if (ass.getLeft().type != Exprent.EXPRENT_FIELD || ass.getRight().type != Exprent.EXPRENT_NEW) { + break; + } + FieldExprent mapField = (FieldExprent)ass.getLeft(); + NewExprent _new = ((NewExprent)ass.getRight()); + if (!mapField.getClassname().equals(node.classStruct.qualifiedName) || !potentialFields.contains(mapField.getName()) || + _new.getNewtype().type != CodeConstants.TYPE_INT || _new.getNewtype().arraydim != 1 || + _new.getLstDims().size() != 1 || _new.getLstDims().get(0).type != Exprent.EXPRENT_FUNCTION) { + break; + } + FunctionExprent func = (FunctionExprent)_new.getLstDims().get(0); + if (func.getFunctype() != FunctionExprent.FUNCTION_ARRAYLENGTH || func.getLstOperands().size() != 1 || func.getLstOperands().get(0).type != Exprent.EXPRENT_INVOCATION) { + break; + } + InvocationExprent invoc = (InvocationExprent)func.getLstOperands().get(0); + if (!"values".equals(invoc.getName()) || !("()[L" + invoc.getClassname() + ";").equals(invoc.getStringDescriptor())) { + break; + } + + String fieldName = mapField.getName(); + String enumName = invoc.getClassname(); + Map idToName = new HashMap(); + + boolean replace = false; + int y = x; + while (++y < seq.getStats().size()) { + if (seq.getStats().get(y).type != Statement.TYPE_TRYCATCH) { + break; + } + CatchStatement _try = (CatchStatement)seq.getStats().get(y); + Statement first = _try.getFirst(); + List exprents = first.getExprents(); + if (_try.getVars().size() != 1 || !"java/lang/NoSuchFieldError".equals(_try.getVars().get(0).getVartype().value) || + first.type != Statement.TYPE_BASICBLOCK || exprents == null || exprents.size() != 1 || exprents.get(0).type != Exprent.EXPRENT_ASSIGNMENT) { + break; + } + ass = (AssignmentExprent)exprents.get(0); + if (ass.getRight().type != Exprent.EXPRENT_CONST || (!(((ConstExprent)ass.getRight()).getValue() instanceof Integer)) || + ass.getLeft().type != Exprent.EXPRENT_ARRAY){ + break; + } + ArrayExprent array = (ArrayExprent)ass.getLeft(); + if (array.getArray().type != Exprent.EXPRENT_FIELD || !array.getArray().equals(mapField) || array.getIndex().type != Exprent.EXPRENT_INVOCATION) { + break; + } + invoc = (InvocationExprent)array.getIndex(); + if (!enumName.equals(invoc.getClassname()) || !"ordinal".equals(invoc.getName()) || !"()I".equals(invoc.getStringDescriptor()) || + invoc.getInstance().type != Exprent.EXPRENT_FIELD) { + break; + } + + FieldExprent enumField = (FieldExprent)invoc.getInstance(); + if (!enumName.equals(enumField.getClassname()) || !enumField.isStatic()) { + break; + } + + idToName.put((Integer)((ConstExprent)ass.getRight()).getValue(), enumField.getName()); + seq.replaceStatement(_try, getNewEmptyStatement()); + replace = true; + } + + if (replace) { + seq.replaceStatement(seq.getStats().get(x), getNewEmptyStatement()); + node.classStruct.enumSwitchMap.put(fieldName, idToName); + node.wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fieldName, "[I")); + } + x = y; + } + } + + private Statement getNewEmptyStatement() { + BasicBlockStatement bstat = new BasicBlockStatement(new BasicBlock( + DecompilerContext.getCounterContainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER))); + bstat.setExprents(new ArrayList()); + return bstat; + } + private static void setLambdaVars(ClassNode parent, ClassNode child) { - if (child.lambda_information.is_method_reference) { // method reference, no code and no parameters + if (parent.wrapper == null || child.lambda_information.is_method_reference) { // method reference, no code and no parameters return; } @@ -147,6 +278,7 @@ public class NestedClassProcessor { if (expr.type == Exprent.EXPRENT_NEW) { NewExprent new_expr = (NewExprent)expr; + VarNamesCollector enclosingCollector = new VarNamesCollector(encmeth.varproc.getVarNames()); if (new_expr.isLambda() && lambda_class_type.equals(new_expr.getNewtype())) { InvocationExprent inv_dynamic = new_expr.getConstructor(); @@ -154,16 +286,16 @@ public class NestedClassProcessor { int param_index = is_static_lambda_content ? 0 : 1; int varindex = is_static_lambda_content ? 0 : 1; - for (int i = 0; i < vars_count; ++i) { - - Exprent param = inv_dynamic.getLstParameters().get(param_index + i); - - if (param.type == Exprent.EXPRENT_VAR) { - VarVersionPaar enc_varpaar = new VarVersionPaar((VarExprent)param); - String enc_varname = encmeth.varproc.getVarName(enc_varpaar); + for (int i = 0; i < md_content.params.length; ++i) { + VarVersionPaar varVersion = new VarVersionPaar(varindex, 0, ExprProcessor.getCastTypeName(md_content.params[i].copy()), false); + if (i < vars_count) { + Exprent param = inv_dynamic.getLstParameters().get(param_index + i); - //meth.varproc.setVarName(new VarVersionPaar(varindex, 0), enc_varname); - mapNewNames.put(new VarVersionPaar(varindex, 0), enc_varname); + if (param.type == Exprent.EXPRENT_VAR) { + mapNewNames.put(varVersion, encmeth.varproc.getVarName(new VarVersionPaar((VarExprent) param))); + } + } else { + mapNewNames.put(varVersion, enclosingCollector.getFreeName(meth.varproc.getVarName(varVersion))); } varindex += md_content.params[i].stack_size; @@ -222,7 +354,7 @@ public class NestedClassProcessor { if (!hasEnclosing) { if (child.type == ClassNode.CLASS_ANONYMOUS) { String message = "Unreferenced anonymous class " + child.classStruct.qualifiedName + "!"; - DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.TRACE); } else if (child.type == ClassNode.CLASS_LOCAL) { String message = "Unreferenced local class " + child.classStruct.qualifiedName + "!"; @@ -276,7 +408,7 @@ public class NestedClassProcessor { HashMap> mask = getMaskLocalVars(nd.wrapper); if (mask.isEmpty()) { String message = "Nested class " + nd.classStruct.qualifiedName + " has no constructor!"; - DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.TRACE); } else { mapVarMasks.put(nd.classStruct.qualifiedName, mask); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java index e283fdf..da591c8 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/ExprProcessor.java @@ -41,6 +41,7 @@ import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; import java.util.*; +import org.jetbrains.java.decompiler.util.Util; public class ExprProcessor implements CodeConstants { @@ -757,10 +758,18 @@ public class ExprProcessor implements CodeConstants { } public static String jmpWrapper(Statement stat, int indent, boolean semicolon, BytecodeMappingTracer tracer) { - StringBuilder buf = new StringBuilder(stat.toJava(indent, tracer)); - String new_line_separator = DecompilerContext.getNewLineSeparator(); + StringBuilder buf = new StringBuilder(); + String content = stat.toJava(indent, tracer); + if (!(stat instanceof IfStatement) && !(stat instanceof DoStatement) && !(stat instanceof SequenceStatement)) { + content = Util.rtrim(content); + } + buf.append(content); + if (!(stat instanceof IfStatement) && !(stat instanceof DoStatement) && !(stat instanceof SequenceStatement) && buf.length() != 0) { + buf.append(new_line_separator); + } + List lstSuccs = stat.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL); if (lstSuccs.size() == 1) { StatEdge edge = lstSuccs.get(0); @@ -814,10 +823,38 @@ public class ExprProcessor implements CodeConstants { String new_line_separator = DecompilerContext.getNewLineSeparator(); StringBuilder buf = new StringBuilder(); + // Spigot Start + boolean inVar = false; + boolean bloodySpecialCaseForNewAssignments = false; + // Spigot End for (Exprent expr : lst) { String content = expr.toJava(indent, tracer); if (content.length() > 0) { + if (expr instanceof VarExprent || expr instanceof AssignmentExprent) { + inVar = true; + boolean wasASpecialLittleFlower = bloodySpecialCaseForNewAssignments; + bloodySpecialCaseForNewAssignments = expr instanceof VarExprent; + if (expr instanceof AssignmentExprent && ((AssignmentExprent) expr).getLeft() instanceof VarExprent) { + AssignmentExprent assignmentExprent = (AssignmentExprent) expr; + VarExprent var = (VarExprent) assignmentExprent.getLeft(); + bloodySpecialCaseForNewAssignments = var.isDefinition(); + } + if (wasASpecialLittleFlower && !bloodySpecialCaseForNewAssignments) { + buf.append(new_line_separator); + } + } else if (inVar) { + inVar = false; + if (!(expr instanceof InvocationExprent + || expr instanceof ExitExprent + || expr instanceof FunctionExprent + ) + || bloodySpecialCaseForNewAssignments) { + bloodySpecialCaseForNewAssignments = false; + buf.append(new_line_separator); + } + } + if (expr.type != Exprent.EXPRENT_VAR || !((VarExprent)expr).isClassdef()) { buf.append(indstr); } @@ -877,7 +914,7 @@ public class ExprProcessor implements CodeConstants { boolean cast = !leftType.isSuperset(rightType) && (rightType.equals(VarType.VARTYPE_OBJECT) || leftType.type != CodeConstants.TYPE_OBJECT); - cast |= castAlways; + cast |= castAlways && !leftType.equals(rightType); // Spigot if (!cast && castNull && rightType.type == CodeConstants.TYPE_NULL) { // check for a nameless anonymous class diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java index 46e8938..d1714cf 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/ConstExprent.java @@ -37,7 +37,7 @@ public class ConstExprent extends Exprent { escapes.put(new Integer(0xA), "\\n"); /* \u000a: linefeed LF */ escapes.put(new Integer(0xC), "\\f"); /* \u000c: form feed FF */ escapes.put(new Integer(0xD), "\\r"); /* \u000d: carriage return CR */ - escapes.put(new Integer(0x22), "\\\""); /* \u0022: double quote " */ + //escapes.put(new Integer(0x22), "\\\""); /* \u0022: double quote " */ escapes.put(new Integer(0x27), "\\\'"); /* \u0027: single quote ' */ escapes.put(new Integer(0x5C), "\\\\"); /* \u005c: backslash \ */ } @@ -298,9 +298,9 @@ public class ConstExprent extends Exprent { case 0x22: //"\\\\\""); // u0022: double quote " buffer.append("\\\""); break; - case 0x27: //"\\\\'"); // u0027: single quote ' - buffer.append("\\\'"); - break; + //case 0x27: //"\\\\'"); // u0027: single quote ' + // buffer.append("\\\'"); + // break; default: if (c >= 32 && c < 127 || !ascii && InterpreterUtil.isPrintableUnicode(c)) { buffer.append(c); @@ -388,6 +388,27 @@ public class ConstExprent extends Exprent { this.consttype = consttype; } + public void adjustConstType(VarType expectedType) { + // BYTECHAR and SHORTCHAR => CHAR in the CHAR context + if ((expectedType.equals(VarType.VARTYPE_CHAR) || expectedType.equals(VarType.VARTYPE_CHARACTER)) && + (consttype.equals(VarType.VARTYPE_BYTECHAR) || consttype.equals(VarType.VARTYPE_SHORTCHAR))) { + int intValue = getIntValue(); + if (isPrintableAscii(intValue) || escapes.containsKey(intValue)) { + setConsttype(VarType.VARTYPE_CHAR); + } + } + // BYTE, BYTECHAR, SHORTCHAR, SHORT, CHAR => INT in the INT context + else if ((expectedType.equals(VarType.VARTYPE_INT) || expectedType.equals(VarType.VARTYPE_INTEGER)) && + consttype.type_family == CodeConstants.TYPE_FAMILY_INTEGER) { + setConsttype(VarType.VARTYPE_INT); + } + } + + private static boolean isPrintableAscii(int c) { + return c >= 32 && c < 127; + } + + public Object getValue() { return value; } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java index e51615d..5363477 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/FieldExprent.java @@ -102,7 +102,7 @@ public class FieldExprent extends Exprent { if (isStatic) { ClassNode node = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); - if (node == null || !classname.equals(node.classStruct.qualifiedName)) { + if (true || node == null || !classname.equals(node.classStruct.qualifiedName)) { // Spigot buf.append(DecompilerContext.getImportCollector().getShortName(ExprProcessor.buildJavaClassName(classname))); buf.append("."); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java index 92d549e..e837f90 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/InvocationExprent.java @@ -59,6 +59,8 @@ public class InvocationExprent extends Exprent { private boolean isStatic; + private boolean canIgnoreBoxing = true; + private int functype = TYP_GENERAL; private Exprent instance; @@ -124,6 +126,7 @@ public class InvocationExprent extends Exprent { isStatic = true; } else { + if (!lstParameters.isEmpty()) instance = lstParameters .get(0); // FIXME: remove the first parameter completely from the list. It's the object type for a virtual lambda method. } @@ -140,12 +143,14 @@ public class InvocationExprent extends Exprent { name = expr.getName(); classname = expr.getClassname(); isStatic = expr.isStatic(); + canIgnoreBoxing = expr.canIgnoreBoxing; functype = expr.getFunctype(); instance = expr.getInstance(); if (instance != null) { instance = instance.copy(); } invocationTyp = expr.getInvocationTyp(); + invoke_dynamic_classsuffix = expr.getInvokeDynamicClassSuffix(); stringDescriptor = expr.getStringDescriptor(); descriptor = expr.getDescriptor(); lstParameters = new ArrayList(expr.getLstParameters()); @@ -197,38 +202,18 @@ public class InvocationExprent extends Exprent { tracer.addMapping(bytecode); - if (invocationTyp == INVOKE_DYNAMIC) { - // ClassNode node = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASSNODE); - // - // if(node != null) { - // ClassNode lambda_node = DecompilerContext.getClassprocessor().getMapRootClasses().get(node.classStruct.qualifiedName + invoke_dynamic_classsuffix); - // if(lambda_node != null) { - // - // String typename = ExprProcessor.getCastTypeName(lambda_node.anonimousClassType); - // - // StringWriter strwriter = new StringWriter(); - // BufferedWriter bufstrwriter = new BufferedWriter(strwriter); - // - // ClassWriter clwriter = new ClassWriter(); - // - // try { - // bufstrwriter.write("new " + typename + "() {"); - // bufstrwriter.newLine(); - // - // - // - // bufstrwriter.flush(); - // } catch(IOException ex) { - // throw new RuntimeException(ex); - // } - // - // buf.append(strwriter.toString()); - // - // } - // } - + if (instance instanceof InvocationExprent) { + ((InvocationExprent) instance).markUsingBoxingResult(); } - else if (isStatic) { + + if (isStatic) { + if (isBoxingCall() && canIgnoreBoxing) { + // process general "boxing" calls, e.g. 'Object[] data = { true }' or 'Byte b = 123' + // here 'byte' and 'short' values do not need an explicit narrowing type cast + TextBuffer buf2 = new TextBuffer(); + ExprProcessor.getCastedExprent(lstParameters.get(0), descriptor.params[0], buf2, indent, false, false, tracer); + return buf2.toString(); + } ClassNode node = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); if (node == null || !classname.equals(node.classStruct.qualifiedName)) { @@ -278,6 +263,12 @@ public class InvocationExprent extends Exprent { else { String res = instance.toJava(indent, tracer); + if (isUnboxingCall()) { + // we don't print the unboxing call - no need to bother with the instance wrapping / casting + buf.append(res); + return buf.toString(); + } + VarType rightType = instance.getExprType(); VarType leftType = new VarType(CodeConstants.TYPE_OBJECT, 0, classname); @@ -353,18 +344,29 @@ public class InvocationExprent extends Exprent { Set setAmbiguousParameters = getAmbiguousParameters(); + // omit 'new Type[] {}' for the last parameter of a vararg method call + if (lstParameters.size() == descriptor.params.length && isVarArgCall()) { + Exprent lastParam = lstParameters.get(lstParameters.size() - 1); + if (lastParam.type == EXPRENT_NEW && lastParam.getExprType().arraydim >= 1) { + ((NewExprent) lastParam).setVarArgParam(true); + } + } + boolean firstpar = true; int start = isEnum ? 2 : 0; for (int i = start; i < lstParameters.size(); i++) { if (sigFields == null || sigFields.get(i) == null) { - if (!firstpar) { - buf.append(", "); - } - TextBuffer buff = new TextBuffer(); ExprProcessor.getCastedExprent(lstParameters.get(i), descriptor.params[i], buff, indent, true, setAmbiguousParameters.contains(i), tracer); - buf.append(buff); + + // the last "new Object[0]" in the vararg call is not printed + if (buff.length() > 0) { + if (!firstpar) { + buf.append(", "); + } + buf.append(buff); + } firstpar = false; } } @@ -373,13 +375,115 @@ public class InvocationExprent extends Exprent { return buf.toString(); } + private boolean isVarArgCall() { + StructClass cl = DecompilerContext.getStructContext().getClass(classname); + if (cl != null) { + StructMethod mt = cl.getMethod(InterpreterUtil.makeUniqueKey(name, stringDescriptor)); + if (mt != null) { + return mt.hasModifier(CodeConstants.ACC_VARARGS); + } + } + else { + // TODO: try to check the class on the classpath + } + return false; + } + + private boolean isBoxingCall() { + if (isStatic && "valueOf".equals(name) && lstParameters.size() == 1) { + int paramType = lstParameters.get(0).getExprType().type; + + // special handling for ambiguous types + if (lstParameters.get(0).type == Exprent.EXPRENT_CONST) { + // 'Integer.valueOf(1)' has '1' type detected as TYPE_BYTECHAR + // 'Integer.valueOf(40_000)' has '40_000' type detected as TYPE_CHAR + // so we check the type family instead + if (lstParameters.get(0).getExprType().type_family == CodeConstants.TYPE_FAMILY_INTEGER) { + if (classname.equals("java/lang/Integer")) { + return true; + } + } + + if (paramType == CodeConstants.TYPE_BYTECHAR || paramType == CodeConstants.TYPE_SHORTCHAR) { + if (classname.equals("java/lang/Character")) { + return true; + } + } + } + + return classname.equals(getClassNameForPrimitiveType(paramType)); + } + + return false; + } + + public void markUsingBoxingResult() { + canIgnoreBoxing = false; + } + + // TODO: move to CodeConstants ??? + private static String getClassNameForPrimitiveType(int type) { + switch (type) { + case CodeConstants.TYPE_BOOLEAN: + return "java/lang/Boolean"; + case CodeConstants.TYPE_BYTE: + case CodeConstants.TYPE_BYTECHAR: + return "java/lang/Byte"; + case CodeConstants.TYPE_CHAR: + return "java/lang/Character"; + case CodeConstants.TYPE_SHORT: + case CodeConstants.TYPE_SHORTCHAR: + return "java/lang/Short"; + case CodeConstants.TYPE_INT: + return "java/lang/Integer"; + case CodeConstants.TYPE_LONG: + return "java/lang/Long"; + case CodeConstants.TYPE_FLOAT: + return "java/lang/Float"; + case CodeConstants.TYPE_DOUBLE: + return "java/lang/Double"; + } + return null; + } + + private static final Map UNBOXING_METHODS; + + static { + UNBOXING_METHODS = new HashMap(); + UNBOXING_METHODS.put("booleanValue", "java/lang/Boolean"); + UNBOXING_METHODS.put("byteValue", "java/lang/Byte"); + UNBOXING_METHODS.put("shortValue", "java/lang/Short"); + UNBOXING_METHODS.put("intValue", "java/lang/Integer"); + UNBOXING_METHODS.put("longValue", "java/lang/Long"); + UNBOXING_METHODS.put("floatValue", "java/lang/Float"); + UNBOXING_METHODS.put("doubleValue", "java/lang/Double"); + UNBOXING_METHODS.put("charValue", "java/lang/Character"); + } + + private boolean isUnboxingCall() { + return !isStatic && lstParameters.size() == 0 && classname.equals(UNBOXING_METHODS.get(name)); + } + + private Set addAllSuper(Set set, String clazz) { + StructClass cstr = DecompilerContext.getStructContext().getClass(clazz); + if (cstr == null) { + return set; + } + set.add(cstr); + for (String inter : cstr.getInterfaceNames()) { + addAllSuper(set, inter); + } + addAllSuper(set, cstr.superClass.getString()); + + return set; + } + private Set getAmbiguousParameters() { Set ret = new HashSet(); - StructClass cstr = DecompilerContext.getStructContext().getClass(classname); - if (cstr != null) { - List lstMethods = new ArrayList(); + List lstMethods = new ArrayList(); + for (StructClass cstr : addAllSuper(new HashSet(), classname)) { for (StructMethod meth : cstr.getMethods()) { if (name.equals(meth.getName())) { MethodDescriptor md = MethodDescriptor.parseDescriptor(meth.getDescriptor()); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java index bb55e45..a31755c 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/NewExprent.java @@ -44,6 +44,8 @@ public class NewExprent extends Exprent { private List lstArrayElements = new ArrayList(); + private boolean isVarArgParam; + private boolean directArrayInit; private boolean anonymous; @@ -353,6 +355,30 @@ public class NewExprent extends Exprent { } } } + else if (isVarArgParam) { + // just print the array elements + VarType leftType = new VarType(newtype.type, newtype.arraydim - 1, newtype.value); + for (int i = 0; i < lstArrayElements.size(); i++) { + if (i > 0) { + buf.append(", "); + } + + // new String[][]{{"abc"}, {"DEF"}} => new String[]{"abc"}, new String[]{"DEF"} + Exprent element = lstArrayElements.get(i); + if (element.type == EXPRENT_NEW) { + ((NewExprent) element).setDirectArrayInit(false); + } + ExprProcessor.getCastedExprent(element, leftType, buf, indent, false, tracer); + } + + // if there is just one element of Object[] type it needs to be casted to resolve ambiguity + if (lstArrayElements.size() == 1) { + VarType elementType = lstArrayElements.get(0).getExprType(); + if (elementType.type == CodeConstants.TYPE_OBJECT && elementType.value.equals("java/lang/Object") && elementType.arraydim >= 1) { + buf.insert(0, "(Object)"); + } + } + } else { buf.append("new ").append(ExprProcessor.getTypeName(newtype)); @@ -485,6 +511,10 @@ public class NewExprent extends Exprent { this.directArrayInit = directArrayInit; } + public void setVarArgParam(boolean isVarArgParam) { + this.isVarArgParam = isVarArgParam; + } + public boolean isLambda() { return lambda; } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java index 16e6c64..8588c8e 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/exps/VarExprent.java @@ -94,15 +94,16 @@ public class VarExprent extends Exprent { } else { String name = null; + String typeName = ExprProcessor.getCastTypeName(getVartype()); // Spigot if (processor != null) { - name = processor.getVarName(new VarVersionPaar(index, version)); + name = processor.getVarName(new VarVersionPaar(index, version, typeName, false)); // Spigot } if (definition) { if (processor != null && processor.getVarFinal(new VarVersionPaar(index, version)) == VarTypeProcessor.VAR_FINALEXPLICIT) { buffer.append("final "); } - buffer.append(ExprProcessor.getCastTypeName(getVartype())).append(" "); + buffer.append(typeName).append(" "); // Spigot } buffer.append(name == null ? ("var" + index + (version == 0 ? "" : "_" + version)) : name); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchAllStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchAllStatement.java index f22d1b1..b993a81 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchAllStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchAllStatement.java @@ -119,7 +119,11 @@ public class CatchAllStatement extends Statement { StringBuilder buf = new StringBuilder(); - buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); + String defs = ExprProcessor.listToJava(varDefinitions, indent, tracer); + buf.append(defs); + if (!defs.isEmpty()) { + buf.append(new_line_separator); + } boolean labeled = isLabeled(); if (labeled) { diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java index a096258..685a60e 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/CatchStatement.java @@ -156,7 +156,11 @@ public class CatchStatement extends Statement { String new_line_separator = DecompilerContext.getNewLineSeparator(); - buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); + String content = ExprProcessor.listToJava(varDefinitions, indent, tracer); + buf.append(content); + if (!content.isEmpty()) { + buf.append(new_line_separator); + } if (isLabeled()) { buf.append(indstr).append("label").append(this.id).append(":").append(new_line_separator); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DoStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DoStatement.java index 1ddce1b..a1ed7ea 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DoStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/DoStatement.java @@ -98,7 +98,11 @@ public class DoStatement extends Statement { String new_line_separator = DecompilerContext.getNewLineSeparator(); - buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); + String content = ExprProcessor.listToJava(varDefinitions, indent, tracer); + buf.append(content); + if (!content.isEmpty()) { + buf.append(new_line_separator); + } if (isLabeled()) { buf.append(indstr).append("label").append(this.id).append(":").append(new_line_separator); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/IfStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/IfStatement.java index ebe2bd7..3e9d368 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/IfStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/IfStatement.java @@ -20,9 +20,9 @@ import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer; import org.jetbrains.java.decompiler.modules.decompiler.DecHelper; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; -import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.IfExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.Util; import java.util.ArrayList; import java.util.List; @@ -207,7 +207,40 @@ public class IfStatement extends Statement { String new_line_separator = DecompilerContext.getNewLineSeparator(); buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); - buf.append(first.toJava(indent, tracer)); + + boolean bloodySpecialCases = false; + if (first instanceof BasicBlockStatement) { + List exps = first.getExprents(); + if (exps.size() != 0) { + Exprent last = exps.get(exps.size() - 1); + if (last instanceof AssignmentExprent) { + AssignmentExprent assignmentExprent = (AssignmentExprent) exps.get(exps.size() - 1); + bloodySpecialCases = true; + if (assignmentExprent.getLeft() instanceof VarExprent) { + VarExprent var = (VarExprent) assignmentExprent.getLeft(); + bloodySpecialCases = !var.isDefinition(); + } + } else if (last instanceof InvocationExprent) { + bloodySpecialCases = true; + } + } + } + + if (bloodySpecialCases) { + buf.append(Util.rtrim(first.toJava(indent, tracer))).append(new_line_separator); + } else { + String content = first.toJava(indent, tracer); + buf.append(content); + if (first instanceof BasicBlockStatement && !content.isEmpty()) { + List exps = first.getExprents(); + if (exps.size() != 0) { + Exprent e = exps.get(exps.size() - 1); + if (!(e instanceof InvocationExprent || e instanceof FunctionExprent)) { + buf.append(new_line_separator); + } + } + } + } if (isLabeled()) { buf.append(indstr).append("label").append(this.id).append(":").append(new_line_separator); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java index aea44de..d805fa9 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SwitchStatement.java @@ -23,11 +23,11 @@ import org.jetbrains.java.decompiler.main.collectors.CounterContainer; import org.jetbrains.java.decompiler.modules.decompiler.DecHelper; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; -import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; -import org.jetbrains.java.decompiler.modules.decompiler.exps.SwitchExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.gen.VarType; import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.Util; import java.util.*; @@ -114,15 +114,36 @@ public class SwitchStatement extends Statement { String new_line_separator = DecompilerContext.getNewLineSeparator(); StringBuilder buf = new StringBuilder(); - buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); - buf.append(first.toJava(indent, tracer)); + String content = ExprProcessor.listToJava(varDefinitions, indent, tracer); + buf.append(content); + if (!content.isEmpty()) { + buf.append(new_line_separator); + } + content = first.toJava(indent, tracer); + buf.append(content); + if (first instanceof BasicBlockStatement && !content.isEmpty()) { + List exps = first.getExprents(); + if (exps.size() != 0) { + Exprent e = exps.get(exps.size() - 1); + if (!(e instanceof InvocationExprent + || e instanceof FunctionExprent + || (e instanceof AssignmentExprent && !(((AssignmentExprent) e).getLeft() instanceof VarExprent && ((VarExprent) ((AssignmentExprent) e).getLeft()).isDefinition())))) { + buf.append(new_line_separator); + } + } + } if (isLabeled()) { buf.append(indstr).append("label").append(this.id).append(":").append(new_line_separator); tracer.incrementCurrentSourceLine(); } - buf.append(indstr).append(headexprent.get(0).toJava(indent, tracer)).append(" {").append(new_line_separator); + // Doesn't seem to be a better place to put it so enhance things here + Map remaps = enhanceHead(headexprent.get(0), buf, indent, tracer); + + if (remaps == null) { + buf.append(indstr).append(headexprent.get(0).toJava(indent, tracer)).append(" {").append(new_line_separator); + } tracer.incrementCurrentSourceLine(); VarType switch_type = headexprent.get(0).getExprType(); @@ -142,12 +163,23 @@ public class SwitchStatement extends Statement { ConstExprent value = (ConstExprent)values.get(j).copy(); value.setConsttype(switch_type); - buf.append(indstr).append("case ").append(value.toJava(indent, tracer)).append(":").append(new_line_separator); + buf.append(indstr).append("case "); + if (remaps == null) { + buf.append(value.toJava(indent, tracer)); + } else { + buf.append(remaps.get(value.getValue())); + } + buf.append(":").append(new_line_separator); tracer.incrementCurrentSourceLine(); } } - buf.append(ExprProcessor.jmpWrapper(stat, indent + 1, false, tracer)); + String c = Util.rtrim(ExprProcessor.jmpWrapper(stat, indent + 1, false, tracer)); + if (!c.isEmpty()) { + buf.append(c); + // buf.append(new_line_separator); + } + if (i != caseStatements.size() - 1) buf.append(new_line_separator); } buf.append(indstr).append("}").append(new_line_separator); @@ -156,6 +188,44 @@ public class SwitchStatement extends Statement { return buf.toString(); } + private Map enhanceHead(Exprent exprent, StringBuilder buf, int indent, BytecodeMappingTracer tracer) { + if (exprent.type != Exprent.EXPRENT_SWITCH) return null; + + SwitchExprent swtch = (SwitchExprent)exprent; + if (swtch.getValue().type != Exprent.EXPRENT_ARRAY) return null; + + ArrayExprent array = (ArrayExprent)swtch.getValue(); + if (array.getArray().type != Exprent.EXPRENT_FIELD || array.getIndex().type != Exprent.EXPRENT_INVOCATION) return null; + + FieldExprent field = (FieldExprent)array.getArray(); + InvocationExprent invoc = (InvocationExprent)array.getIndex(); + StructClass cls = DecompilerContext.getStructContext().getClass(field.getClassname()); + if (cls == null || !field.isStatic() || !"ordinal".equals(invoc.getName()) || !"()I".equals(invoc.getStringDescriptor())) return null; + + Map ret = cls.enumSwitchMap.get(field.getName()); + if (ret == null) return null; + + for (List lst : getCaseValues()) { + if (lst != null) { + for (ConstExprent cst : lst) { + if (cst != null && (!(cst.getValue() instanceof Integer) || !ret.containsKey(cst.getValue()))) { + return null; + } + } + } + } + + tracer.addMapping(swtch.bytecode); + tracer.addMapping(field.bytecode); + tracer.addMapping(invoc.bytecode); + + String indstr = InterpreterUtil.getIndentString(indent); + String new_line_separator = DecompilerContext.getNewLineSeparator(); + + buf.append(indstr).append("switch (").append((invoc.getInstance().toJava(indent, tracer))).append(") {").append(new_line_separator); + return ret; + } + public void initExprents() { SwitchExprent swexpr = (SwitchExprent)first.getExprents().remove(first.getExprents().size() - 1); swexpr.setCaseValues(caseValues); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SynchronizedStatement.java b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SynchronizedStatement.java index eb94ddc..3ac68a6 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SynchronizedStatement.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/stats/SynchronizedStatement.java @@ -20,7 +20,7 @@ import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; import org.jetbrains.java.decompiler.modules.decompiler.SequenceHelper; import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; -import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; import org.jetbrains.java.decompiler.util.InterpreterUtil; import java.util.ArrayList; @@ -75,8 +75,26 @@ public class SynchronizedStatement extends Statement { String new_line_separator = DecompilerContext.getNewLineSeparator(); StringBuilder buf = new StringBuilder(); - buf.append(ExprProcessor.listToJava(varDefinitions, indent, tracer)); - buf.append(first.toJava(indent, tracer)); + + String content = ExprProcessor.listToJava(varDefinitions, indent, tracer); + buf.append(content); + if (!content.isEmpty()) { + buf.append(new_line_separator); + } + + content = first.toJava(indent, tracer); + buf.append(content); + if (first instanceof BasicBlockStatement && !content.isEmpty()) { + List exps = first.getExprents(); + if (exps.size() != 0) { + Exprent e = exps.get(exps.size() - 1); + if (!(e instanceof InvocationExprent + || e instanceof FunctionExprent + || (e instanceof AssignmentExprent && !(((AssignmentExprent) e).getLeft() instanceof VarExprent && ((VarExprent) ((AssignmentExprent) e).getLeft()).isDefinition())))) { + buf.append(new_line_separator); + } + } + } if (isLabeled()) { buf.append(indstr).append("label").append(this.id).append(":").append(new_line_separator); diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java index 266ba94..18314d8 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java @@ -21,6 +21,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.VarHelper; // Spigot import java.util.*; import java.util.Map.Entry; @@ -36,6 +37,8 @@ public class VarProcessor { private HashSet externvars = new HashSet(); + private VarHelper helper = new VarHelper(); // Spigot + public void setVarVersions(RootStatement root) { varvers = new VarVersionsProcessor(); @@ -104,13 +107,23 @@ public class VarProcessor { } public String getVarName(VarVersionPaar varpaar) { - return mapVarNames == null ? null : mapVarNames.get(varpaar); + // Spigot Start + String name = mapVarNames.get(varpaar); + if (name != null) { + mapVarNames.put(varpaar, name = helper.help(name, varpaar.type, varpaar.varargs)); + } + return name; + // Spigot End } public void setVarName(VarVersionPaar varpaar, String name) { mapVarNames.put(varpaar, name); } + public Collection getVarNames() { + return mapVarNames != null ? mapVarNames.values() : Collections.EMPTY_SET; + } + public int getVarFinal(VarVersionPaar varpaar) { return varvers == null ? VarTypeProcessor.VAR_FINAL : varvers.getVarFinal(varpaar); } diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionPaar.java b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionPaar.java index 5f3e520..065673f 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionPaar.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarVersionPaar.java @@ -21,6 +21,8 @@ public class VarVersionPaar { public int var; public int version; + public String type; + public boolean varargs; private int hashCode = -1; @@ -29,9 +31,11 @@ public class VarVersionPaar { this.version = version; } - public VarVersionPaar(Integer var, Integer version) { - this.var = var.intValue(); - this.version = version.intValue(); + public VarVersionPaar(int var, int version, String type, boolean varargs) { + this.var = var; + this.version = version; + this.type = type; + this.varargs = varargs; } public VarVersionPaar(VarExprent var) { diff --git a/src/org/jetbrains/java/decompiler/struct/StructClass.java b/src/org/jetbrains/java/decompiler/struct/StructClass.java index cb24ff6..13f5875 100644 --- a/src/org/jetbrains/java/decompiler/struct/StructClass.java +++ b/src/org/jetbrains/java/decompiler/struct/StructClass.java @@ -24,6 +24,8 @@ import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.VBStyleCollection; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /* class_file { @@ -58,6 +60,7 @@ public class StructClass extends StructMember { private final String[] interfaceNames; private final VBStyleCollection fields; private final VBStyleCollection methods; + public final Map> enumSwitchMap = new HashMap>(); private ConstantPool pool; diff --git a/src/org/jetbrains/java/decompiler/struct/gen/VarType.java b/src/org/jetbrains/java/decompiler/struct/gen/VarType.java index a58898a..d09ee59 100644 --- a/src/org/jetbrains/java/decompiler/struct/gen/VarType.java +++ b/src/org/jetbrains/java/decompiler/struct/gen/VarType.java @@ -39,6 +39,8 @@ public class VarType { // TODO: optimize switch public static final VarType VARTYPE_STRING = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/String"); public static final VarType VARTYPE_CLASS = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Class"); public static final VarType VARTYPE_OBJECT = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Object"); + public static final VarType VARTYPE_INTEGER = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Integer"); + public static final VarType VARTYPE_CHARACTER = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/Character"); public static final VarType VARTYPE_VOID = new VarType(CodeConstants.TYPE_VOID); public int type; diff --git a/src/org/jetbrains/java/decompiler/util/Util.java b/src/org/jetbrains/java/decompiler/util/Util.java new file mode 100644 index 0000000..5ade232 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/util/Util.java @@ -0,0 +1,12 @@ +package org.jetbrains.java.decompiler.util; + +import java.util.regex.Pattern; + +public class Util { + + private final static Pattern RTRIM = Pattern.compile("\\s+$"); + + public static String rtrim(String s) { + return RTRIM.matcher(s).replaceAll(""); + } +} diff --git a/src/org/jetbrains/java/decompiler/util/VarHelper.java b/src/org/jetbrains/java/decompiler/util/VarHelper.java new file mode 100644 index 0000000..34b52a9 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/util/VarHelper.java @@ -0,0 +1,104 @@ +package org.jetbrains.java.decompiler.util; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +public class VarHelper { + + private static final Map switches = new HashMap(); + + static { + switches.put("byte", new String[]{ + "b" + }); + switches.put("char", new String[]{ + "c" + }); + switches.put("short", new String[]{ + "short" + }); + switches.put("int", new String[]{ + "i", "j", "k", "l" + }); + switches.put("long", new String[]{ + "i", "j", "k", "l" + }); + switches.put("boolean", new String[]{ + "flag" + }); + switches.put("double", new String[]{ + "d" + }); + switches.put("float", new String[]{ + "f", "f" // Add twice because the original script is inconsistent + }); + switches.put("String", new String[]{ + "s", "s" // Add twice because the original script is inconsistent + }); + switches.put("Class", new String[]{ + "oclass" + }); + switches.put("Long", new String[]{ + "olong" + }); + switches.put("Byte", new String[]{ + "obyte" + }); + switches.put("Short", new String[]{ + "oshort" + }); + switches.put("Boolean", new String[]{ + "obool" + }); + switches.put("Double", new String[]{ + "odouble" + }); + switches.put("Float", new String[]{ + "ofloat" + }); + switches.put("Long", new String[]{ + "olong" + }); + switches.put("Enum", new String[]{ + "oenum" + }); + } + private final Set used = new HashSet(); + + public String help(String name, String type, boolean varArgs) { + if (type == null || !name.startsWith("var")) { + return name; + } + + while (type.contains( "<" )) { + type = type.substring(0, type.indexOf('<')) + type.substring(type.lastIndexOf('>') + 1); + } + type = type.replace( '.', '_' ); + + if (type.endsWith("]")) { + type = "a" + type.substring(0, type.indexOf('[')); + } else if (varArgs) { + type = "a" + type; + } + + String[] remap = switches.get(type); + if (remap == null) { + remap = new String[]{ + type.toLowerCase(Locale.ENGLISH) + }; + } + + for (int counter = 0;; counter++) { + for (String subMap : remap) { + String attempt = subMap + ((counter == 0 && !subMap.equals("short") && (remap.length > 1 || subMap.length() > 1)) ? "" : counter); + if (!used.contains(attempt)) { + used.add(attempt); + return attempt; + } + } + } + } +} -- cgit v1.2.3