diff options
Diffstat (limited to 'src/org/jetbrains/java/decompiler/main')
26 files changed, 6117 insertions, 0 deletions
diff --git a/src/org/jetbrains/java/decompiler/main/AssertProcessor.java b/src/org/jetbrains/java/decompiler/main/AssertProcessor.java new file mode 100644 index 0000000..86e5c59 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/AssertProcessor.java @@ -0,0 +1,321 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.SecondaryFunctionsHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StatEdge; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssertExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExitExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.BasicBlockStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.IfStatement; +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.struct.StructField; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class AssertProcessor { + + private static final VarType CLASS_ASSERTION_ERROR = new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/AssertionError"); + + public static void buildAssertions(ClassNode node) { + + ClassWrapper wrapper = node.wrapper; + + StructField field = findAssertionField(node); + + if(field != null) { + + String key = InterpreterUtil.makeUniqueKey(field.getName(), field.getDescriptor()); + + boolean res = false; + + for(MethodWrapper meth : wrapper.getMethods()) { + RootStatement root = meth.root; + if(root != null) { + res |= replaceAssertions(root, wrapper.getClassStruct().qualifiedName, key); + } + } + + if(res) { + // hide the helper field + wrapper.getHideMembers().add(key); + } + } + + } + + private static StructField findAssertionField(ClassNode node) { + + ClassWrapper wrapper = node.wrapper; + + boolean nosynthflag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + for(StructField fd: wrapper.getClassStruct().getFields()) { + + String keyField = InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()); + + // initializer exists + if(wrapper.getStaticFieldInitializers().containsKey(keyField)) { + + int flags = fd.access_flags; + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || fd.getAttributes().containsKey("Synthetic"); + + // access flags set + if((flags & CodeConstants.ACC_STATIC) != 0 && (flags & CodeConstants.ACC_FINAL) != 0 && + (isSynthetic || nosynthflag)) { + + // field type boolean + FieldDescriptor fdescr = FieldDescriptor.parseDescriptor(fd.getDescriptor()); + if(VarType.VARTYPE_BOOLEAN.equals(fdescr.type)) { + + Exprent initializer = wrapper.getStaticFieldInitializers().getWithKey(keyField); + if(initializer.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fexpr = (FunctionExprent)initializer; + + if(fexpr.getFunctype() == FunctionExprent.FUNCTION_BOOLNOT && + fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_INVOCATION) { + + InvocationExprent invexpr = (InvocationExprent)fexpr.getLstOperands().get(0); + + if(invexpr.getInstance() != null && invexpr.getInstance().type == Exprent.EXPRENT_CONST && "desiredAssertionStatus".equals(invexpr.getName()) + && "java/lang/Class".equals(invexpr.getClassname()) && invexpr.getLstParameters().isEmpty()) { + + ConstExprent cexpr = (ConstExprent)invexpr.getInstance(); + if(VarType.VARTYPE_CLASS.equals(cexpr.getConsttype())) { + + ClassNode nd = node; + while(nd != null) { + if(nd.wrapper.getClassStruct().qualifiedName.equals(cexpr.getValue())) { + break; + } + nd = nd.parent; + } + + if(nd != null) { // found enclosing class with the same name + return fd; + } + } + } + } + } + } + } + } + } + + + return null; + } + + + private static boolean replaceAssertions(Statement statement, String classname, String key) { + + boolean res = false; + + for(Statement st : statement.getStats()) { + res |= replaceAssertions(st, classname, key); + } + + boolean replaced = true; + while(replaced) { + replaced = false; + + for(Statement st : statement.getStats()) { + if(st.type == Statement.TYPE_IF) { + if(replaceAssertion(statement, (IfStatement)st, classname, key)) { + replaced = true; + break; + } + } + } + + res |= replaced; + } + + return res; + } + + private static boolean replaceAssertion(Statement parent, IfStatement stat, String classname, String key) { + + Statement ifstat = stat.getIfstat(); + InvocationExprent throwError = isAssertionError(ifstat); + + if(throwError == null) { + return false; + } + + Object[] exprres = getAssertionExprent(stat.getHeadexprent().getCondition().copy(), classname, key); + if(!(Boolean)exprres[1]) { + return false; + } + + List<Exprent> lstParams = new ArrayList<Exprent>(); + + Exprent ascond = null, retcond = null; + if(exprres[0] != null) { + ascond = new FunctionExprent(FunctionExprent.FUNCTION_BOOLNOT, + Arrays.asList(new Exprent[]{(Exprent)exprres[0]})); + retcond = SecondaryFunctionsHelper.propagateBoolNot(ascond); + } + + lstParams.add(retcond==null?ascond:retcond); + if(!throwError.getLstParameters().isEmpty()) { + lstParams.add(throwError.getLstParameters().get(0)); + } + + AssertExprent asexpr = new AssertExprent(lstParams); + + Statement newstat = new BasicBlockStatement(new BasicBlock( + DecompilerContext.getCountercontainer().getCounterAndIncrement(CounterContainer.STATEMENT_COUNTER))); + newstat.setExprents(Arrays.asList(new Exprent[] {asexpr})); + + Statement first = stat.getFirst(); + + if(stat.iftype == IfStatement.IFTYPE_IFELSE || (first.getExprents() != null && + !first.getExprents().isEmpty())) { + + first.removeSuccessor(stat.getIfEdge()); + first.removeSuccessor(stat.getElseEdge()); + + List<Statement> lstStatements = new ArrayList<Statement>(); + if(first.getExprents() != null && !first.getExprents().isEmpty()) { + lstStatements.add(first); + } + lstStatements.add(newstat); + if(stat.iftype == IfStatement.IFTYPE_IFELSE) { + lstStatements.add(stat.getElsestat()); + } + + SequenceStatement sequence = new SequenceStatement(lstStatements); + sequence.setAllParent(); + + for(int i=0;i<sequence.getStats().size()-1;i++) { + sequence.getStats().get(i).addSuccessor(new StatEdge(StatEdge.TYPE_REGULAR, + sequence.getStats().get(i), sequence.getStats().get(i+1))); + } + + if(stat.iftype == IfStatement.IFTYPE_IFELSE) { + Statement ifelse = stat.getElsestat(); + + List<StatEdge> lstSuccs = ifelse.getAllSuccessorEdges(); + if(!lstSuccs.isEmpty()) { + StatEdge endedge = lstSuccs.get(0); + if(endedge.closure == stat) { + sequence.addLabeledEdge(endedge); + } + } + } + + newstat = sequence; + } + + newstat.getVarDefinitions().addAll(stat.getVarDefinitions()); + parent.replaceStatement(stat, newstat); + + return true; + } + + private static InvocationExprent isAssertionError(Statement stat) { + + if(stat == null || stat.getExprents() == null || stat.getExprents().size() !=1) { + return null; + } + + Exprent expr = stat.getExprents().get(0); + + if(expr.type == Exprent.EXPRENT_EXIT) { + ExitExprent exexpr = (ExitExprent)expr; + if(exexpr.getExittype() == ExitExprent.EXIT_THROW && exexpr.getValue().type == Exprent.EXPRENT_NEW) { + NewExprent nexpr = (NewExprent)exexpr.getValue(); + if(CLASS_ASSERTION_ERROR.equals(nexpr.getNewtype()) && nexpr.getConstructor() != null) { + return nexpr.getConstructor(); + } + } + } + + return null; + } + + private static Object[] getAssertionExprent(Exprent exprent, String classname, String key) { + + if(exprent.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fexpr = (FunctionExprent)exprent; + if(fexpr.getFunctype() == FunctionExprent.FUNCTION_CADD) { + + for(int i=0;i<2;i++) { + Exprent param = fexpr.getLstOperands().get(i); + + if(isAssertionField(param, classname, key)) { + return new Object[] {fexpr.getLstOperands().get(1-i), true}; + } + } + + for(int i=0;i<2;i++) { + Exprent param = fexpr.getLstOperands().get(i); + + Object[] res = getAssertionExprent(param, classname, key); + if((Boolean)res[1]) { + if(param != res[0]) { + fexpr.getLstOperands().set(i, (Exprent)res[0]); + } + return new Object[] {fexpr, true}; + } + } + } else if(isAssertionField(fexpr, classname, key)) { + // assert false; + return new Object[] {null, true}; + } + } + + return new Object[] {exprent, false}; + } + + private static boolean isAssertionField(Exprent exprent, String classname, String key) { + + if(exprent.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fparam = (FunctionExprent)exprent; + if(fparam.getFunctype() == FunctionExprent.FUNCTION_BOOLNOT && + fparam.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD) { + FieldExprent fdparam = (FieldExprent)fparam.getLstOperands().get(0); + if(classname.equals(fdparam.getClassname()) + && key.equals(InterpreterUtil.makeUniqueKey(fdparam.getName(), fdparam.getDescriptor().descriptorString))) { + return true; + } + } + } + + return false; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java b/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java new file mode 100644 index 0000000..cdad6ca --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java @@ -0,0 +1,309 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ConstExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExitExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FunctionExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; +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.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public class ClassReference14Processor { + + public ExitExprent bodyexprent; + + public ExitExprent handlerexprent; + + + public ClassReference14Processor() { + + InvocationExprent invfor = new InvocationExprent(); + invfor.setName("forName"); + invfor.setClassname("java/lang/Class"); + invfor.setStringDescriptor("(Ljava/lang/String;)Ljava/lang/Class;"); + invfor.setDescriptor(MethodDescriptor.parseDescriptor("(Ljava/lang/String;)Ljava/lang/Class;")); + invfor.setStatic(true); + invfor.setLstParameters(Arrays.asList(new Exprent[] {new VarExprent(0, VarType.VARTYPE_STRING, null)})); + + bodyexprent = new ExitExprent(ExitExprent.EXIT_RETURN, + invfor, + VarType.VARTYPE_CLASS); + + InvocationExprent constr = new InvocationExprent(); + constr.setName("<init>"); + constr.setClassname("java/lang/NoClassDefFoundError"); + constr.setStringDescriptor("()V"); + constr.setFunctype(InvocationExprent.TYP_INIT); + constr.setDescriptor(MethodDescriptor.parseDescriptor("()V")); + + NewExprent newexpr = new NewExprent(new VarType(CodeConstants.TYPE_OBJECT,0,"java/lang/NoClassDefFoundError"), new ArrayList<Exprent>()); + newexpr.setConstructor(constr); + + InvocationExprent invcause = new InvocationExprent(); + invcause.setName("initCause"); + invcause.setClassname("java/lang/NoClassDefFoundError"); + invcause.setStringDescriptor("(Ljava/lang/Throwable;)Ljava/lang/Throwable;"); + invcause.setDescriptor(MethodDescriptor.parseDescriptor("(Ljava/lang/Throwable;)Ljava/lang/Throwable;")); + invcause.setInstance(newexpr); + invcause.setLstParameters(Arrays.asList(new Exprent[] {new VarExprent(2, new VarType(CodeConstants.TYPE_OBJECT, 0, "java/lang/ClassNotFoundException"), null)})); + + handlerexprent = new ExitExprent(ExitExprent.EXIT_THROW, + invcause, + null); + } + + + public void processClassReferences(ClassNode node) { + + ClassWrapper wrapper = node.wrapper; + +// int major_version = wrapper.getClassStruct().major_version; +// int minor_version = wrapper.getClassStruct().minor_version; +// +// if(major_version > 48 || (major_version == 48 && minor_version > 0)) { +// // version 1.5 or above +// return; +// } + + if(wrapper.getClassStruct().isVersionGE_1_5()) { + // version 1.5 or above + return; + } + + // find the synthetic method Class class$(String) if present + HashMap<ClassWrapper, MethodWrapper> mapClassMeths = new HashMap<ClassWrapper, MethodWrapper>(); + findClassMethod(node, mapClassMeths); + + if(mapClassMeths.isEmpty()) { + return; + } + + HashSet<ClassWrapper> setFound = new HashSet<ClassWrapper>(); + processClassRec(node, mapClassMeths, setFound); + + if(!setFound.isEmpty()) { + for(ClassWrapper wrp : setFound) { + StructMethod mt = mapClassMeths.get(wrp).methodStruct; + wrp.getHideMembers().add(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + } + } + + } + + private void processClassRec(ClassNode node, final HashMap<ClassWrapper, MethodWrapper> mapClassMeths, final HashSet<ClassWrapper> setFound) { + + final ClassWrapper wrapper = node.wrapper; + + // search code + for(MethodWrapper meth : wrapper.getMethods()) { + + RootStatement root = meth.root; + if(root != null) { + + DirectGraph graph = meth.getOrBuildGraph(); + + graph.iterateExprents(new DirectGraph.ExprentIterator() { + public int processExprent(Exprent exprent) { + for(Entry<ClassWrapper, MethodWrapper> ent : mapClassMeths.entrySet()) { + if(replaceInvocations(exprent, ent.getKey(), ent.getValue())) { + setFound.add(ent.getKey()); + } + } + return 0; + } + }); + + } + } + + // search initializers + for(int j=0;j<2;j++) { + VBStyleCollection<Exprent, String> initializers = j==0?wrapper.getStaticFieldInitializers():wrapper.getDynamicFieldInitializers(); + + for(int i=0; i<initializers.size();i++) { + for(Entry<ClassWrapper, MethodWrapper> ent : mapClassMeths.entrySet()) { + Exprent exprent = initializers.get(i); + if(replaceInvocations(exprent, ent.getKey(), ent.getValue())) { + setFound.add(ent.getKey()); + } + + String cl = isClass14Invocation(exprent, ent.getKey(), ent.getValue()); + if(cl != null) { + initializers.set(i, new ConstExprent(VarType.VARTYPE_CLASS, cl.replace('.', '/'))); + setFound.add(ent.getKey()); + } + } + } + } + + // iterate nested classes + for(ClassNode nd : node.nested) { + processClassRec(nd, mapClassMeths, setFound); + } + + } + + private void findClassMethod(ClassNode node, HashMap<ClassWrapper, MethodWrapper> mapClassMeths) { + + boolean nosynthflag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + ClassWrapper wrapper = node.wrapper; + + for(MethodWrapper meth : wrapper.getMethods()) { + StructMethod mt = meth.methodStruct; + + if(((mt.getAccessFlags() & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic") + || nosynthflag) && + mt.getDescriptor().equals("(Ljava/lang/String;)Ljava/lang/Class;") && + (mt.getAccessFlags() & CodeConstants.ACC_STATIC) != 0) { + + RootStatement root = meth.root; + if(root != null) { + if(root.getFirst().type == Statement.TYPE_TRYCATCH) { + CatchStatement cst = (CatchStatement)root.getFirst(); + if(cst.getStats().size() == 2 && cst.getFirst().type == Statement.TYPE_BASICBLOCK && + cst.getStats().get(1).type == Statement.TYPE_BASICBLOCK && + cst.getVars().get(0).getVartype().equals(new VarType(CodeConstants.TYPE_OBJECT,0,"java/lang/ClassNotFoundException"))) { + + BasicBlockStatement body = (BasicBlockStatement)cst.getFirst(); + BasicBlockStatement handler = (BasicBlockStatement)cst.getStats().get(1); + + if(body.getExprents().size() == 1 && handler.getExprents().size() == 1) { + if(bodyexprent.equals(body.getExprents().get(0)) && + handlerexprent.equals(handler.getExprents().get(0))) { + mapClassMeths.put(wrapper, meth); + break; + } + } + } + } + } + } + } + + // iterate nested classes + for(ClassNode nd : node.nested) { + findClassMethod(nd, mapClassMeths); + } + + } + + + private boolean replaceInvocations(Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) { + + boolean res = false; + + for(;;) { + + boolean found = false; + + for(Exprent expr : exprent.getAllExprents()) { + String cl = isClass14Invocation(expr, wrapper, meth); + if(cl != null) { + exprent.replaceExprent(expr, new ConstExprent(VarType.VARTYPE_CLASS, cl.replace('.', '/'))); + found = true; + res = true; + break; + } + + res |= replaceInvocations(expr, wrapper, meth); + } + + if(!found) { + break; + } + } + + return res; + } + + + + private String isClass14Invocation(Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) { + + if(exprent.type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent fexpr = (FunctionExprent)exprent; + if(fexpr.getFunctype() == FunctionExprent.FUNCTION_IIF) { + if(fexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FUNCTION) { + FunctionExprent headexpr = (FunctionExprent)fexpr.getLstOperands().get(0); + if(headexpr.getFunctype() == FunctionExprent.FUNCTION_EQ) { + if(headexpr.getLstOperands().get(0).type == Exprent.EXPRENT_FIELD && + headexpr.getLstOperands().get(1).type == Exprent.EXPRENT_CONST && + ((ConstExprent)headexpr.getLstOperands().get(1)).getConsttype().equals(VarType.VARTYPE_NULL)) { + + FieldExprent field = (FieldExprent)headexpr.getLstOperands().get(0); + ClassNode fieldnode = DecompilerContext.getClassprocessor().getMapRootClasses().get(field.getClassname()); + + if(fieldnode != null && fieldnode.classStruct.qualifiedName.equals(wrapper.getClassStruct().qualifiedName)) { // source class + StructField fd = wrapper.getClassStruct().getField(field.getName(), field.getDescriptor().descriptorString); // FIXME: can be null! why?? + + if(fd != null && (fd.access_flags & CodeConstants.ACC_STATIC) != 0 && + ((fd.access_flags & CodeConstants.ACC_SYNTHETIC) != 0 || fd.getAttributes().containsKey("Synthetic") + || DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET))) { + + if(fexpr.getLstOperands().get(1).type == Exprent.EXPRENT_ASSIGNMENT && fexpr.getLstOperands().get(2).equals(field)) { + AssignmentExprent asexpr = (AssignmentExprent)fexpr.getLstOperands().get(1); + + if(asexpr.getLeft().equals(field) && asexpr.getRight().type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent)asexpr.getRight(); + + if(invexpr.getClassname().equals(wrapper.getClassStruct().qualifiedName) && + invexpr.getName().equals(meth.methodStruct.getName()) && + invexpr.getStringDescriptor().equals(meth.methodStruct.getDescriptor())) { + + if(invexpr.getLstParameters().get(0).type == Exprent.EXPRENT_CONST) { + wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); // hide synthetic field + return ((ConstExprent)invexpr.getLstParameters().get(0)).getValue().toString(); + } + } + } + } + } + } + } + } + } + } + } + + return null; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java new file mode 100644 index 0000000..c14fe79 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -0,0 +1,1107 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AnnotationExprent; +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.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarTypeProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructAnnDefaultAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructAnnotationAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructAnnotationParameterAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructConstantValueAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructExceptionsAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGenericSignatureAttribute; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericClassDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericFieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericMain; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericMethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.generics.GenericType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public class ClassWriter { + + private static final int[] modval_class = new int[] {CodeConstants.ACC_PUBLIC, CodeConstants.ACC_PROTECTED, CodeConstants.ACC_PRIVATE, + CodeConstants.ACC_ABSTRACT, CodeConstants.ACC_STATIC, CodeConstants.ACC_FINAL, CodeConstants.ACC_STRICT}; + + private static final String[] modstr_class = new String[] {"public ", "protected ", "private ", "abstract ", "static ", "final ", "strictfp "}; + + private static final int[] modval_field = new int[] {CodeConstants.ACC_PUBLIC, CodeConstants.ACC_PROTECTED, CodeConstants.ACC_PRIVATE, + CodeConstants.ACC_STATIC, CodeConstants.ACC_FINAL, CodeConstants.ACC_TRANSIENT, CodeConstants.ACC_VOLATILE}; + + private static final String[] modstr_field = new String[] {"public ", "protected ", "private ", "static ", "final ", "transient ", "volatile "}; + + private static final int[] modval_meth = new int[] {CodeConstants.ACC_PUBLIC, CodeConstants.ACC_PROTECTED, CodeConstants.ACC_PRIVATE, + CodeConstants.ACC_ABSTRACT, CodeConstants.ACC_STATIC, CodeConstants.ACC_FINAL, CodeConstants.ACC_SYNCHRONIZED, + CodeConstants.ACC_NATIVE, CodeConstants.ACC_STRICT}; + + private static final String[] modstr_meth = new String[] {"public ", "protected ", "private ", "abstract ", "static ", "final ", "synchronized ", "native ", "strictfp "}; + + private static final HashSet<Integer> mod_notinterface = new HashSet<Integer>(Arrays.asList(new Integer[] {CodeConstants.ACC_ABSTRACT, CodeConstants.ACC_STATIC})); + private static final HashSet<Integer> mod_notinterface_fields = new HashSet<Integer>(Arrays.asList(new Integer[] {CodeConstants.ACC_PUBLIC, CodeConstants.ACC_STATIC, CodeConstants.ACC_FINAL})); + private static final HashSet<Integer> mod_notinterface_meth = new HashSet<Integer>(Arrays.asList(new Integer[] {CodeConstants.ACC_PUBLIC, CodeConstants.ACC_ABSTRACT})); + + private ClassReference14Processor ref14processor; + + private PoolInterceptor interceptor; + + public ClassWriter() { + ref14processor = new ClassReference14Processor(); + interceptor = DecompilerContext.getPoolInterceptor(); + } + + + private void invokeProcessors(ClassNode node) { + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + InitializerProcessor.extractInitializers(wrapper); + + if(node.type == ClassNode.CLASS_ROOT && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_CLASS_1_4)) { + ref14processor.processClassReferences(node); + } + + if(DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (cl.access_flags & CodeConstants.ACC_ENUM) != 0) { + EnumProcessor.clearEnum(wrapper); + } + + if(DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { + AssertProcessor.buildAssertions(node); + } + + } + + public void classLambdaToJava(ClassNode node, BufferedWriter writer, Exprent method_object, int indent) throws IOException { + + // get the class node with the content method + ClassNode node_content = node; + while(node_content != null && node_content.type == ClassNode.CLASS_LAMBDA) { + node_content = node_content.parent; + } + + if(node_content == null) { + return; + } + + boolean lambda_to_anonymous = DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS); + + ClassNode nodeold = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASSNODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, node); + + ClassWrapper wrapper = node_content.wrapper; + StructClass cl = wrapper.getClassStruct(); + + DecompilerContext.getLogger().startWriteClass(node.simpleName); + + if(node.lambda_information.is_method_reference) { + + if(!node.lambda_information.is_content_method_static && method_object != null) { // reference to a virtual method + writer.write(method_object.toJava(indent)); + } else { // reference to a static method + writer.write(ExprProcessor.getCastTypeName(new VarType(node.lambda_information.content_class_name, false))); + } + + writer.write("::"); + writer.write(node.lambda_information.content_method_name); + + writer.flush(); + + } else { + + // lambda method + StructMethod mt = cl.getMethod(node.lambda_information.content_method_key); + MethodWrapper meth = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + MethodDescriptor md_content = MethodDescriptor.parseDescriptor(node.lambda_information.content_method_descriptor); + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(node.lambda_information.method_descriptor); + + if(!lambda_to_anonymous) { // lambda parameters '() ->' + + StringBuilder buff = new StringBuilder("("); + + boolean firstpar = true; + int index = node.lambda_information.is_content_method_static ? 0 : 1;; + + int start_index = md_content.params.length - md_lambda.params.length; + + for(int i=0;i<md_content.params.length;i++) { + + if(i >= start_index) { + + if(!firstpar) { + buff.append(", "); + } + + String parname = meth.varproc.getVarName(new VarVersionPaar(index, 0)); + buff.append(parname==null ? "param"+index : parname); // null iff decompiled with errors + + firstpar = false; + } + + index+=md_content.params[i].stack_size; + } + buff.append(") ->"); + + writer.write(buff.toString()); + } + + StringWriter strwriter = new StringWriter(); + BufferedWriter bufstrwriter = new BufferedWriter(strwriter); + + if(lambda_to_anonymous) { + methodLambdaToJava(node, node_content, mt, bufstrwriter, indent+1, false); + } else { + methodLambdaToJava(node, node_content, mt, bufstrwriter, indent, true); + } + + bufstrwriter.flush(); + + // closing up class definition + writer.write(" {"); + writer.write(DecompilerContext.getNewLineSeparator()); + + writer.write(strwriter.toString()); + + writer.write(InterpreterUtil.getIndentString(indent)); + writer.write("}"); + writer.flush(); + } + + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, nodeold); + + DecompilerContext.getLogger().endWriteClass(); + } + + public void classToJava(ClassNode node, BufferedWriter writer, int indent) throws IOException { + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + ClassNode nodeold = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASSNODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, node); + + // last minute processing + invokeProcessors(node); + + DecompilerContext.getLogger().startWriteClass(cl.qualifiedName); + + writeClassDefinition(node, writer, indent); + + // methods + StringWriter strwriter = new StringWriter(); + BufferedWriter bufstrwriter = new BufferedWriter(strwriter); + + boolean firstmt = true; + boolean mthidden = false; + + for(StructMethod mt : cl.getMethods()) { + + int flags = mt.getAccessFlags(); + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic"); + boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; + + if((!isSynthetic || !DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)) && + (!isBridge || !DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE)) && + !wrapper.getHideMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()))) { + if(!mthidden && (!firstmt || node.type != ClassNode.CLASS_ANONYMOUS)) { + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + firstmt = false; + } + + mthidden = !methodToJava(node, mt, bufstrwriter, indent+1); + } + } + bufstrwriter.flush(); + + StringWriter strwriter1 = new StringWriter(); + BufferedWriter bufstrwriter1 = new BufferedWriter(strwriter1); + + int fields_count = 0; + + boolean enumfields = false; + + // fields + for(StructField fd: cl.getFields()) { + int flags = fd.access_flags; + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || fd.getAttributes().containsKey("Synthetic"); + if((!isSynthetic || !DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)) + && !wrapper.getHideMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()))) { + + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + if(isEnum) { + if(enumfields) { + bufstrwriter1.write(","); + bufstrwriter1.write(DecompilerContext.getNewLineSeparator()); + } else { + enumfields = true; + } + } else { + if(enumfields) { + bufstrwriter1.write(";"); + bufstrwriter1.write(DecompilerContext.getNewLineSeparator()); + enumfields = false; + } + } + + fieldToJava(wrapper, cl, fd, bufstrwriter1, indent+1); + fields_count++; + } + } + + if(enumfields) { + bufstrwriter1.write(";"); + bufstrwriter1.write(DecompilerContext.getNewLineSeparator()); + } + + bufstrwriter1.flush(); + + if(fields_count > 0) { + writer.write(DecompilerContext.getNewLineSeparator()); + writer.write(strwriter1.toString()); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + + // methods + writer.write(strwriter.toString()); + + // member classes + for(ClassNode inner : node.nested) { + if(inner.type == ClassNode.CLASS_MEMBER) { + StructClass innercl = inner.classStruct; + + boolean isSynthetic = ((inner.access | innercl.access_flags) & CodeConstants.ACC_SYNTHETIC) != 0 || innercl.getAttributes().containsKey("Synthetic"); + if((!isSynthetic || !DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC)) + && !wrapper.getHideMembers().contains(innercl.qualifiedName)) { + writer.write(DecompilerContext.getNewLineSeparator()); + classToJava(inner, writer, indent+1); + } + } + } + + writer.write(InterpreterUtil.getIndentString(indent)); + writer.write("}"); + if(node.type != ClassNode.CLASS_ANONYMOUS) { + writer.write(DecompilerContext.getNewLineSeparator()); + } + writer.flush(); + + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, nodeold); + + DecompilerContext.getLogger().endWriteClass(); + } + + private void writeClassDefinition(ClassNode node, BufferedWriter writer, int indent) throws IOException { + + if(node.type == ClassNode.CLASS_ANONYMOUS) { + writer.write(" {"); + writer.write(DecompilerContext.getNewLineSeparator()); + } else { + + String indstr = InterpreterUtil.getIndentString(indent); + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + int flags = node.type == ClassNode.CLASS_ROOT?cl.access_flags:node.access; + boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0; + boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0; + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + + boolean isDeprecated = cl.getAttributes().containsKey("Deprecated"); + + if(interceptor != null) { + String oldname = interceptor.getOldName(cl.qualifiedName); + if(oldname != null) { + writer.write(indstr); + writer.write("// $FF: renamed from: "+getDescriptorPrintOut(oldname, 0)); + writer.write(DecompilerContext.getNewLineSeparator()); + } + } + + if (isDeprecated) { + writer.write(indstr); + writer.write("/** @deprecated */"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + // class annotations + List<AnnotationExprent> lstAnn = getAllAnnotations(cl.getAttributes()); + for(AnnotationExprent annexpr : lstAnn) { + writer.write(annexpr.toJava(indent)); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.getAttributes().containsKey("Synthetic"); + + if(isSynthetic) { + writer.write(indstr); + writer.write("// $FF: synthetic class"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + writer.write(indstr); + + if(isEnum) { + // remove abstract and final flags (JLS 8.9 Enums) + flags &=~CodeConstants.ACC_ABSTRACT; + flags &=~CodeConstants.ACC_FINAL; + } + + for(int i=0;i<modval_class.length;i++) { + if(!isInterface || !mod_notinterface.contains(modval_class[i])) { + if((flags & modval_class[i]) != 0) { + writer.write(modstr_class[i]); + } + } + } + + if(isEnum) { + writer.write("enum "); + }else if(isInterface) { + if(isAnnotation) { + writer.write("@"); + } + writer.write("interface "); + } else { + writer.write("class "); + } + + GenericClassDescriptor descriptor = null; + if(DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)cl.getAttributes().getWithKey("Signature"); + if(attr != null) { + descriptor = GenericMain.parseClassSignature(attr.getSignature()); + } + } + + writer.write(node.simpleName); + if(descriptor != null && !descriptor.fparameters.isEmpty()) { + writer.write("<"); + for(int i=0;i<descriptor.fparameters.size();i++) { + if(i>0) { + writer.write(", "); + } + writer.write(descriptor.fparameters.get(i)); + + List<GenericType> lstBounds = descriptor.fbounds.get(i); + if (lstBounds.size() > 1 || !"java/lang/Object".equals(lstBounds.get(0).value)) { + writer.write(" extends "); + writer.write(GenericMain.getGenericCastTypeName(lstBounds.get(0))); + + for(int j=1;j<lstBounds.size();j++) { + writer.write(" & " + GenericMain.getGenericCastTypeName(lstBounds.get(j))); + } + } + } + writer.write(">"); + } + writer.write(" "); + + if(!isEnum && !isInterface && cl.superClass != null) { + VarType supertype = new VarType(cl.superClass.getString(), true); + if(!VarType.VARTYPE_OBJECT.equals(supertype)) { + writer.write("extends "); + if(descriptor != null) { + writer.write(GenericMain.getGenericCastTypeName(descriptor.superclass)); + } else { + writer.write(ExprProcessor.getCastTypeName(supertype)); + } + writer.write(" "); + } + } + + if(!isAnnotation) { + int[] interfaces = cl.getInterfaces(); + if(interfaces.length > 0) { + writer.write(isInterface?"extends ":"implements "); + for(int i=0;i<interfaces.length;i++) { + if(i>0) { + writer.write(", "); + } + if(descriptor != null) { + writer.write(GenericMain.getGenericCastTypeName(descriptor.superinterfaces.get(i))); + } else { + writer.write(ExprProcessor.getCastTypeName(new VarType(cl.getInterface(i), true))); + } + } + writer.write(" "); + } + } + + writer.write("{"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + } + + private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, BufferedWriter writer, int indent) throws IOException { + + String indstr = InterpreterUtil.getIndentString(indent); + + boolean isInterface = (cl.access_flags & CodeConstants.ACC_INTERFACE) != 0; + int flags = fd.access_flags; + + if(interceptor != null) { + String oldname = interceptor.getOldName(cl.qualifiedName+" "+fd.getName()+" "+fd.getDescriptor()); + if(oldname != null) { + String[] element = oldname.split(" "); + + writer.write(indstr); + writer.write("// $FF: renamed from: "+element[1]+" "+getDescriptorPrintOut(element[2], 1)); + writer.write(DecompilerContext.getNewLineSeparator()); + } + } + + boolean isDeprecated = fd.getAttributes().containsKey("Deprecated"); + + if (isDeprecated) { + writer.write(indstr); + writer.write("/** @deprecated */"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + // field annotations + List<AnnotationExprent> lstAnn = getAllAnnotations(fd.getAttributes()); + for(AnnotationExprent annexpr : lstAnn) { + writer.write(annexpr.toJava(indent)); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || fd.getAttributes().containsKey("Synthetic"); + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + + if(isSynthetic) { + writer.write(indstr); + writer.write("// $FF: synthetic field"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + writer.write(indstr); + + if(!isEnum) { + for(int i=0;i<modval_field.length;i++) { + if(!isInterface || !mod_notinterface_fields.contains(modval_field[i])) { + if((flags & modval_field[i]) != 0) { + writer.write(modstr_field[i]); + } + } + } + } + + VarType fieldType = new VarType(fd.getDescriptor(), false); + + GenericFieldDescriptor descriptor = null; + if(DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)fd.getAttributes().getWithKey("Signature"); + if(attr != null) { + descriptor = GenericMain.parseFieldSignature(attr.getSignature()); + } + } + + if(!isEnum) { + if(descriptor != null) { + writer.write(GenericMain.getGenericCastTypeName(descriptor.type)); + } else { + writer.write(ExprProcessor.getCastTypeName(fieldType)); + } + writer.write(" "); + } + + writer.write(fd.getName()); + + Exprent initializer; + if((flags & CodeConstants.ACC_STATIC) != 0) { + initializer = wrapper.getStaticFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } else { + initializer = wrapper.getDynamicFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } + + if(initializer != null) { + if(isEnum && initializer.type == Exprent.EXPRENT_NEW) { + NewExprent nexpr = (NewExprent)initializer; + nexpr.setEnumconst(true); + writer.write(nexpr.toJava(indent)); + } else { + writer.write(" = "); + writer.write(initializer.toJava(indent)); + } + } else if((flags & CodeConstants.ACC_FINAL) != 0 && (flags & CodeConstants.ACC_STATIC) != 0) { + StructConstantValueAttribute attr = (StructConstantValueAttribute)fd.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE); + if(attr != null) { + PrimitiveConstant cnst = cl.getPool().getPrimitiveConstant(attr.getIndex()); + writer.write(" = "); + writer.write(new ConstExprent(fieldType, cnst.value).toJava(indent)); + } + } + + if(!isEnum) { + writer.write(";"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + } + + public boolean methodLambdaToJava(ClassNode node_lambda, ClassNode node_content, StructMethod mt, BufferedWriter writer, int indent, boolean code_only) throws IOException { + + ClassWrapper wrapper = node_content.wrapper; + + MethodWrapper meth = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + MethodWrapper methold = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, meth); + + String indstr = InterpreterUtil.getIndentString(indent); + + String method_name = node_lambda.lambda_information.method_name; + MethodDescriptor md_content = MethodDescriptor.parseDescriptor(node_lambda.lambda_information.content_method_descriptor); + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(node_lambda.lambda_information.method_descriptor); + + StringWriter strwriter = new StringWriter(); + BufferedWriter bufstrwriter = new BufferedWriter(strwriter); + + if(!code_only) { + bufstrwriter.write(indstr); + bufstrwriter.write("public "); + bufstrwriter.write(method_name); + bufstrwriter.write("("); + + boolean firstpar = true; + int index = node_lambda.lambda_information.is_content_method_static ? 0 : 1;; + + int start_index = md_content.params.length - md_lambda.params.length; + + for(int i=0;i<md_content.params.length;i++) { + + if(i >= start_index) { + + if(!firstpar) { + bufstrwriter.write(", "); + } + + VarType partype = md_content.params[i].copy(); + + String strpartype = ExprProcessor.getCastTypeName(partype); + if(ExprProcessor.UNDEFINED_TYPE_STRING.equals(strpartype) && DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + strpartype = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + bufstrwriter.write(strpartype); + bufstrwriter.write(" "); + + String parname = meth.varproc.getVarName(new VarVersionPaar(index, 0)); + bufstrwriter.write(parname==null?"param"+index:parname); // null iff decompiled with errors + + firstpar = false; + } + + index+=md_content.params[i].stack_size; + } + + bufstrwriter.write(")"); + bufstrwriter.write(" "); + bufstrwriter.write("{"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + + if(root != null && !meth.decompiledWithErrors) { // check for existence + try { + String code = root.toJava(indent+1); + bufstrwriter.write(code); + } catch(Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method "+mt.getName()+" "+mt.getDescriptor()+" couldn't be written.", ex); + meth.decompiledWithErrors = true; + } + } + + if(meth.decompiledWithErrors) { + bufstrwriter.write(InterpreterUtil.getIndentString(indent+1)); + bufstrwriter.write("// $FF: Couldn't be decompiled"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + if(!code_only) { + bufstrwriter.write(indstr+"}"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + bufstrwriter.flush(); + + writer.write(strwriter.toString()); + + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methold); + + return true; + } + + public boolean methodToJava(ClassNode node, StructMethod mt, BufferedWriter writer, int indent) throws IOException { + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + MethodWrapper meth = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + MethodWrapper methold = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, meth); + + boolean isInterface = (cl.access_flags & CodeConstants.ACC_INTERFACE) != 0; + boolean isAnnotation = (cl.access_flags & CodeConstants.ACC_ANNOTATION) != 0; + boolean isEnum = (cl.access_flags & CodeConstants.ACC_ENUM) != 0 && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + boolean isDeprecated = mt.getAttributes().containsKey("Deprecated"); + + String indstr = InterpreterUtil.getIndentString(indent); + boolean clinit = false, init = false, dinit = false; + + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); + + StringWriter strwriter = new StringWriter(); + BufferedWriter bufstrwriter = new BufferedWriter(strwriter); + + int flags = mt.getAccessFlags(); + if((flags & CodeConstants.ACC_NATIVE) != 0) { + flags &= ~CodeConstants.ACC_STRICT; // compiler bug: a strictfp class sets all methods to strictfp + } + + if("<clinit>".equals(mt.getName())) { + flags &= CodeConstants.ACC_STATIC; // ingnore all modifiers except 'static' in a static initializer + } + + if(interceptor != null) { + String oldname = interceptor.getOldName(cl.qualifiedName+" "+mt.getName()+" "+mt.getDescriptor()); + if(oldname != null) { + String[] element = oldname.split(" "); + + bufstrwriter.write(indstr); + bufstrwriter.write("// $FF: renamed from: "+element[1]+" "+getDescriptorPrintOut(element[2], 2)); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + } + + if (isDeprecated) { + writer.write(indstr); + writer.write("/** @deprecated */"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + // method annotations + List<AnnotationExprent> lstAnn = getAllAnnotations(mt.getAttributes()); + for(AnnotationExprent annexpr : lstAnn) { + bufstrwriter.write(annexpr.toJava(indent)); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic"); + boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; + + if(isSynthetic) { + bufstrwriter.write(indstr); + bufstrwriter.write("// $FF: synthetic method"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + if(isBridge) { + bufstrwriter.write(indstr); + bufstrwriter.write("// $FF: bridge method"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + bufstrwriter.write(indstr); + for(int i=0;i<modval_meth.length;i++) { + if(!isInterface || !mod_notinterface_meth.contains(modval_meth[i])) { + if((flags & modval_meth[i]) != 0) { + bufstrwriter.write(modstr_meth[i]); + } + } + } + + // 'default' modifier (Java 8) + if(isInterface && mt.containsCode()) { + bufstrwriter.write("default "); + } + + String name = mt.getName(); + if ("<init>".equals(name)) { + if (node.type == ClassNode.CLASS_ANONYMOUS) { + name = ""; + dinit = true; + } else { + name = node.simpleName; + init = true; + } + } else if ("<clinit>".equals(name)) { + name = ""; + clinit = true; + } + + GenericMethodDescriptor descriptor = null; + if(DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES)) { + StructGenericSignatureAttribute attr = (StructGenericSignatureAttribute)mt.getAttributes().getWithKey("Signature"); + if(attr != null) { + descriptor = GenericMain.parseMethodSignature(attr.getSignature()); + int actualParams = md.params.length; + if(isEnum && init) actualParams -= 2; + if(actualParams != descriptor.params.size()) { + DecompilerContext.getLogger().writeMessage("Inconsistent generic signature in method "+mt.getName()+" "+mt.getDescriptor(), IFernflowerLogger.WARNING); + descriptor = null; + } + } + } + + boolean throwsExceptions = false; + + int param_count_explicit = 0; + + if(!clinit && !dinit) { + + boolean thisvar = (mt.getAccessFlags() & CodeConstants.ACC_STATIC) == 0; + + // formal type parameters + if(descriptor != null && !descriptor.fparameters.isEmpty()) { + bufstrwriter.write("<"); + for(int i=0;i<descriptor.fparameters.size();i++) { + if(i>0) { + bufstrwriter.write(", "); + } + bufstrwriter.write(descriptor.fparameters.get(i)); + + List<GenericType> lstBounds = descriptor.fbounds.get(i); + if (lstBounds.size() > 1 || !"java/lang/Object".equals(lstBounds.get(0).value)) { + bufstrwriter.write(" extends "); + bufstrwriter.write(GenericMain.getGenericCastTypeName(lstBounds.get(0))); + + for(int j = 1; j < lstBounds.size(); j++) { + bufstrwriter.write(" & " + GenericMain.getGenericCastTypeName(lstBounds.get(j))); + } + } + } + bufstrwriter.write("> "); + } + + if(!init) { + if(descriptor != null) { + bufstrwriter.write(GenericMain.getGenericCastTypeName(descriptor.ret)); + } else { + bufstrwriter.write(ExprProcessor.getCastTypeName(md.ret)); + } + bufstrwriter.write(" "); + } + + bufstrwriter.write(name); + bufstrwriter.write("("); + + // parameter annotations + List<List<AnnotationExprent>> lstParAnn = getAllParameterAnnotations(mt.getAttributes()); + + List<VarVersionPaar> signFields = meth.signatureFields; + + // compute last visible parameter + int lastparam_index = -1; + for(int i=0;i<md.params.length;i++) { + if(signFields == null || signFields.get(i) == null) { + lastparam_index = i; + } + } + + boolean firstpar = true; + int index = isEnum && init ? 3 : thisvar ? 1 : 0; + 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++) { + if (signFields == null || signFields.get(i) == null) { + + if(!firstpar) { + bufstrwriter.write(", "); + } + + if(lstParAnn.size() > param_count_explicit) { + List<AnnotationExprent> annotations = lstParAnn.get(param_count_explicit); + for(int j=0;j<annotations.size();j++) { + AnnotationExprent annexpr = annotations.get(j); + if(annexpr.getAnnotationType() == AnnotationExprent.ANNOTATION_NORMAL) { + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + bufstrwriter.write(annexpr.toJava(indent+1)); + } else { + bufstrwriter.write(annexpr.toJava(0)); + } + bufstrwriter.write(" "); + } + } + + if(meth.varproc.getVarFinal(new VarVersionPaar(index, 0)) == VarTypeProcessor.VAR_FINALEXPLICIT) { + bufstrwriter.write("final "); + } + + + if(descriptor != null) { + GenericType partype = descriptor.params.get(i); + + boolean isVarArgs = (i == lastparam_index && (mt.getAccessFlags() & CodeConstants.ACC_VARARGS) != 0 + && partype.arraydim > 0); + + if(isVarArgs) { + partype.arraydim--; + } + + String strpartype = GenericMain.getGenericCastTypeName(partype); + if(ExprProcessor.UNDEFINED_TYPE_STRING.equals(strpartype) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + strpartype = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + bufstrwriter.write(strpartype); + + if(isVarArgs) { + bufstrwriter.write(" ..."); + } + + } else { + VarType partype = md.params[i].copy(); + + boolean isVarArgs = (i == lastparam_index && (mt.getAccessFlags() & CodeConstants.ACC_VARARGS) != 0 + && partype.arraydim > 0); + + if(isVarArgs) { + partype.decArrayDim(); + } + + String strpartype = ExprProcessor.getCastTypeName(partype); + if(ExprProcessor.UNDEFINED_TYPE_STRING.equals(strpartype) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + strpartype = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + bufstrwriter.write(strpartype); + + if(isVarArgs) { + bufstrwriter.write(" ..."); + } + } + + bufstrwriter.write(" "); + String parname = meth.varproc.getVarName(new VarVersionPaar(index, 0)); + bufstrwriter.write(parname==null?"param"+index:parname); // null iff decompiled with errors + firstpar = false; + param_count_explicit++; + } + + index+=md.params[i].stack_size; + } + + bufstrwriter.write(")"); + + StructExceptionsAttribute attr = (StructExceptionsAttribute)mt.getAttributes().getWithKey("Exceptions"); + if((descriptor!=null && !descriptor.exceptions.isEmpty()) || attr != null) { + throwsExceptions = true; + bufstrwriter.write(" throws "); + + for(int i=0;i<attr.getThrowsExceptions().size();i++) { + if(i>0) { + bufstrwriter.write(", "); + } + if(descriptor!=null && !descriptor.exceptions.isEmpty()) { + bufstrwriter.write(GenericMain.getGenericCastTypeName(descriptor.exceptions.get(i))); + } else { + VarType exctype = new VarType(attr.getExcClassname(i, cl.getPool()), true); + bufstrwriter.write(ExprProcessor.getCastTypeName(exctype)); + } + } + } + } + + boolean hidemethod = false; + + if((flags & (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_NATIVE)) != 0) { // native or abstract method (explicit or interface) + if(isAnnotation) { + StructAnnDefaultAttribute attr = (StructAnnDefaultAttribute)mt.getAttributes().getWithKey("AnnotationDefault"); + if(attr != null) { + bufstrwriter.write(" default "); + bufstrwriter.write(attr.getDefaultValue().toJava(indent+1)); + } + } + + bufstrwriter.write(";"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } else { + if(!clinit && !dinit) { + bufstrwriter.write(" "); + } + bufstrwriter.write("{"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + + RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + + if(root != null && !meth.decompiledWithErrors) { // check for existence + try { + String code = root.toJava(indent+1); + + boolean singleinit = false; + if(init && param_count_explicit == 0 && !throwsExceptions && DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) { + int init_counter = 0; + for(MethodWrapper mth : wrapper.getMethods()) { + if("<init>".equals(mth.methodStruct.getName())) { + init_counter++; + } + } + singleinit = (init_counter == 1); + } + + hidemethod = (clinit || dinit || singleinit) && code.length() == 0; + + bufstrwriter.write(code); + } catch(Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method "+mt.getName()+" "+mt.getDescriptor()+" couldn't be written.", ex); + meth.decompiledWithErrors = true; + } + } + + if(meth.decompiledWithErrors) { + bufstrwriter.write(InterpreterUtil.getIndentString(indent+1)); + bufstrwriter.write("// $FF: Couldn't be decompiled"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + bufstrwriter.write(indstr+"}"); + bufstrwriter.write(DecompilerContext.getNewLineSeparator()); + } + + bufstrwriter.flush(); + + if(!hidemethod) { + writer.write(strwriter.toString()); + } + + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methold); + + return !hidemethod; + } + + private List<AnnotationExprent> getAllAnnotations(VBStyleCollection<StructGeneralAttribute, String> attributes) { + + String[] annattrnames = new String[] {StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, + StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; + + List<AnnotationExprent> lst = new ArrayList<AnnotationExprent>(); + + for(String attrname : annattrnames) { + StructAnnotationAttribute attr = (StructAnnotationAttribute)attributes.getWithKey(attrname); + if(attr != null) { + lst.addAll(attr.getAnnotations()); + } + } + + return lst; + } + + private List<List<AnnotationExprent>> getAllParameterAnnotations(VBStyleCollection<StructGeneralAttribute, String> attributes) { + + String[] annattrnames = new String[] {StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, + StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; + + List<List<AnnotationExprent>> ret = new ArrayList<List<AnnotationExprent>>(); + + for(String attrname : annattrnames) { + StructAnnotationParameterAttribute attr = (StructAnnotationParameterAttribute)attributes.getWithKey(attrname); + if(attr != null) { + for(int i=0;i<attr.getParamAnnotations().size();i++) { + List<AnnotationExprent> lst = new ArrayList<AnnotationExprent>(); + boolean isnew = (ret.size()<=i); + + if(!isnew) { + lst = ret.get(i); + } + lst.addAll(attr.getParamAnnotations().get(i)); + + if(isnew) { + ret.add(lst); + } else { + ret.set(i, lst); + } + } + } + } + + return ret; + } + + private String getDescriptorPrintOut(String descriptor, int element) { + + switch(element) { + case 0: // class + return ExprProcessor.buildJavaClassName(descriptor); + case 1: // field + return getTypePrintOut(FieldDescriptor.parseDescriptor(descriptor).type); + case 2: // method + default: + MethodDescriptor md = MethodDescriptor.parseDescriptor(descriptor); + + StringBuilder buffer = new StringBuilder("("); + + boolean first = true; + for(VarType partype : md.params) { + if(first) { + first = false; + } else { + buffer.append(", "); + } + buffer.append(getTypePrintOut(partype)); + } + buffer.append(") "); + buffer.append(getTypePrintOut(md.ret)); + + return buffer.toString(); + } + } + + private String getTypePrintOut(VarType type) { + String strtype = ExprProcessor.getCastTypeName(type, false); + if(ExprProcessor.UNDEFINED_TYPE_STRING.equals(strtype) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + strtype = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, false); + } + return strtype; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java new file mode 100644 index 0000000..e0e144f --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -0,0 +1,449 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.StringWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Set; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.extern.IIdentifierRenamer; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.LambdaProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedClassProcessor; +import org.jetbrains.java.decompiler.main.rels.NestedMemberAccess; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class ClassesProcessor { + + private HashMap<String, ClassNode> mapRootClasses = new HashMap<String, ClassNode>(); + + public ClassesProcessor(StructContext context) { + + HashMap<String, Object[]> mapInnerClasses = new HashMap<String, Object[]>(); + + HashMap<String, HashSet<String>> mapNestedClassReferences = new HashMap<String, HashSet<String>>(); + HashMap<String, HashSet<String>> mapEnclosingClassReferences = new HashMap<String, HashSet<String>>(); + + HashMap<String, String> mapNewSimpleNames = new HashMap<String, String>(); + + boolean bDecompileInner = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_INNER); + + // create class nodes + for(StructClass cl: context.getClasses().values()) { + if(cl.isOwn() && !mapRootClasses.containsKey(cl.qualifiedName)) { + + if(bDecompileInner) { + StructInnerClassesAttribute inner = (StructInnerClassesAttribute)cl.getAttributes().getWithKey("InnerClasses"); + if(inner != null) { + + for(int i=0;i<inner.getClassentries().size();i++) { + + int[] entry = inner.getClassentries().get(i); + String[] strentry = inner.getStringentries().get(i); + + Object[] arr = new Object[4]; // arr[0] not used + + String innername = strentry[0]; + + // nested class type + arr[2] = entry[1] == 0?(entry[2]==0?ClassNode.CLASS_ANONYMOUS:ClassNode.CLASS_LOCAL):ClassNode.CLASS_MEMBER; + + // original simple name + String simpleName = strentry[2]; + String savedName = mapNewSimpleNames.get(innername); + + if(savedName != null) { + simpleName = savedName; + } else if(simpleName != null && DecompilerContext.getOption(IFernflowerPreferences.RENAME_ENTITIES)) { + IIdentifierRenamer renamer = DecompilerContext.getPoolInterceptor().getHelper(); + if(renamer.toBeRenamed(IIdentifierRenamer.ELEMENT_CLASS, simpleName, null, null)) { + simpleName = renamer.getNextClassname(innername, simpleName); + mapNewSimpleNames.put(innername, simpleName); + } + } + + arr[1] = simpleName; + + // original access flags + arr[3] = entry[3]; + + // enclosing class + String enclClassName = null; + if(entry[1] != 0) { + enclClassName = strentry[1]; + } else { + enclClassName = cl.qualifiedName; + } + + if(!innername.equals(enclClassName)) { // self reference + StructClass enclosing_class = context.getClasses().get(enclClassName); + if(enclosing_class != null && enclosing_class.isOwn()) { // own classes only + + Object[] arrold = mapInnerClasses.get(innername); + if(arrold == null) { + mapInnerClasses.put(innername, arr); + } else { + if(!InterpreterUtil.equalObjectArrays(arrold, arr)){ + DecompilerContext.getLogger().writeMessage("Inconsistent inner class entries for "+innername+"!", IFernflowerLogger.WARNING); + } + } + + // reference to the nested class + HashSet<String> set = mapNestedClassReferences.get(enclClassName); + if(set == null) { + mapNestedClassReferences.put(enclClassName, set = new HashSet<String>()); + } + set.add(innername); + + // reference to the enclosing class + set = mapEnclosingClassReferences.get(innername); + if(set == null) { + mapEnclosingClassReferences.put(innername, set = new HashSet<String>()); + } + set.add(enclClassName); + } + } + } + } + } + + ClassNode node = new ClassNode(ClassNode.CLASS_ROOT, cl); + node.access = cl.access_flags; + mapRootClasses.put(cl.qualifiedName, node); + } + } + + if(bDecompileInner) { + + // connect nested classes + for(Entry<String, ClassNode> ent: mapRootClasses.entrySet()) { + // root class? + if(!mapInnerClasses.containsKey(ent.getKey())) { + + HashSet<String> setVisited = new HashSet<String>(); + LinkedList<String> stack = new LinkedList<String>(); + + stack.add(ent.getKey()); + setVisited.add(ent.getKey()); + + while(!stack.isEmpty()) { + + String superClass = stack.removeFirst(); + ClassNode supernode = mapRootClasses.get(superClass); + + HashSet<String> setNestedClasses = mapNestedClassReferences.get(superClass); + if(setNestedClasses != null) { + + StructClass scl = supernode.classStruct; + StructInnerClassesAttribute inner = (StructInnerClassesAttribute) scl.getAttributes().getWithKey("InnerClasses"); + for(int i = 0; i < inner.getStringentries().size(); i++) { + String nestedClass = inner.getStringentries().get(i)[0]; + if (!setNestedClasses.contains(nestedClass)) { + continue; + } + + if(setVisited.contains(nestedClass)) { + continue; + } + setVisited.add(nestedClass); + + ClassNode nestednode = mapRootClasses.get(nestedClass); + if(nestednode == null) { + DecompilerContext.getLogger().writeMessage("Nested class "+nestedClass+" missing!", IFernflowerLogger.WARNING); + continue; + } + + Object[] arr = mapInnerClasses.get(nestedClass); + + if((Integer)arr[2] == ClassNode.CLASS_MEMBER) { + // FIXME: check for consistent naming + } + + nestednode.type = (Integer)arr[2]; + nestednode.simpleName = (String)arr[1]; + nestednode.access = (Integer)arr[3]; + + if(nestednode.type == ClassNode.CLASS_ANONYMOUS) { + StructClass cl = nestednode.classStruct; + + // remove static if anonymous class + // a common compiler bug + nestednode.access &= ~CodeConstants.ACC_STATIC; + + int[] interfaces = cl.getInterfaces(); + + if(interfaces.length > 0) { + if(interfaces.length > 1) { + DecompilerContext.getLogger().writeMessage("Inconsistent anonymous class definition: "+cl.qualifiedName, IFernflowerLogger.WARNING); + } + nestednode.anonimousClassType = new VarType(cl.getInterface(0), true); + } else { + nestednode.anonimousClassType = new VarType(cl.superClass.getString(), true); + } + } else if(nestednode.type == ClassNode.CLASS_LOCAL) { + // only abstract and final are permitted + // a common compiler bug + nestednode.access &= (CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_FINAL); + } + + supernode.nested.add(nestednode); + nestednode.parent = supernode; + + nestednode.enclosingClasses.addAll(mapEnclosingClassReferences.get(nestedClass)); + + stack.add(nestedClass); + } + } + } + } + } + + } + + } + + + public void writeClass(StructContext context, StructClass cl, BufferedWriter outwriter) throws IOException { + + ClassNode root = mapRootClasses.get(cl.qualifiedName); + if(root.type != ClassNode.CLASS_ROOT) { + return; + } + + try { + DecompilerContext.setImpcollector(new ImportCollector(root)); + DecompilerContext.setCountercontainer(new CounterContainer()); + + // lambda processing + LambdaProcessor lambda_proc = new LambdaProcessor(); + lambda_proc.processClass(root); + + // add simple class names to implicit import + addClassnameToImport(root, DecompilerContext.getImpcollector()); + // build wrappers for all nested classes + // that's where the actual processing takes place + initWrappers(root); + + NestedClassProcessor nestedproc = new NestedClassProcessor(); + nestedproc.processClass(root, root); + + NestedMemberAccess nstmember = new NestedMemberAccess(); + nstmember.propagateMemberAccess(root); + + ClassWriter clwriter = new ClassWriter(); + + StringWriter strwriter = new StringWriter(); + clwriter.classToJava(root, new BufferedWriter(strwriter), 0); + + if(DecompilerContext.getOption(IFernflowerPreferences.OUTPUT_COPYRIGHT_COMMENT)) { + outwriter.write("// Decompiled by: Fernflower "+Fernflower.version); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Date: "+new SimpleDateFormat("dd.MM.yyyy HH:mm:ss").format(new Date())); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Copyright: 2008-2010, Stiver"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write("// Home page: http://www.reversed-java.com"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write(DecompilerContext.getNewLineSeparator()); + } + + int index = cl.qualifiedName.lastIndexOf("/"); + if(index >= 0) { + String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); + outwriter.write("package "); + outwriter.write(packageName); + outwriter.write(";"); + outwriter.write(DecompilerContext.getNewLineSeparator()); + outwriter.write(DecompilerContext.getNewLineSeparator()); + } + + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASSNODE, root); + + DecompilerContext.getImpcollector().writeImports(outwriter); + outwriter.write(DecompilerContext.getNewLineSeparator()); + + outwriter.write(strwriter.toString()); + outwriter.flush(); + + } finally { + destroyWrappers(root); + } + } + + private void initWrappers(ClassNode node) throws IOException { + + if(node.type == ClassNode.CLASS_LAMBDA) { + return; + } + + ClassWrapper wrapper = new ClassWrapper(node.classStruct); + wrapper.init(); + + node.wrapper = wrapper; + + for(ClassNode nd: node.nested) { + initWrappers(nd); + } + } + + private void addClassnameToImport(ClassNode node, ImportCollector imp) { + + if(node.simpleName != null && node.simpleName.length() > 0) { + imp.getShortName(node.type == ClassNode.CLASS_ROOT?node.classStruct.qualifiedName:node.simpleName, false); + } + + for(ClassNode nd: node.nested) { + addClassnameToImport(nd, imp); + } + } + + private void destroyWrappers(ClassNode node) { + + node.wrapper = null; + node.classStruct.releaseResources(); + + for(ClassNode nd: node.nested) { + destroyWrappers(nd); + } + } + + public HashMap<String, ClassNode> getMapRootClasses() { + return mapRootClasses; + } + + + public class ClassNode { + + public static final int CLASS_ROOT = 0; + public static final int CLASS_MEMBER = 1; + public static final int CLASS_ANONYMOUS = 2; + public static final int CLASS_LOCAL = 4; + public static final int CLASS_LAMBDA = 8; + + public int type; + + public int access; + + public String simpleName; + + public StructClass classStruct; + + public ClassWrapper wrapper; + + public String enclosingMethod; + + public InvocationExprent superInvocation; + + public HashMap<String, VarVersionPaar> mapFieldsToVars = new HashMap<String, VarVersionPaar>(); + + public VarType anonimousClassType; + + public List<ClassNode> nested = new ArrayList<ClassNode>(); + + public Set<String> enclosingClasses = new HashSet<String>(); + + public ClassNode parent; + + public LambdaInformation lambda_information; + + public ClassNode(String content_class_name, String content_method_name, String content_method_descriptor, int content_method_invokation_type, + String lambda_class_name, String lambda_method_name, String lambda_method_descriptor, StructClass classStruct) { // lambda class constructor + this.type = CLASS_LAMBDA; + this.classStruct = classStruct; // 'parent' class containing the static function + + lambda_information = new LambdaInformation(); + + lambda_information.class_name = lambda_class_name; + lambda_information.method_name = lambda_method_name; + lambda_information.method_descriptor = lambda_method_descriptor; + + lambda_information.content_class_name = content_class_name; + lambda_information.content_method_name = content_method_name; + lambda_information.content_method_descriptor = content_method_descriptor; + lambda_information.content_method_invokation_type = content_method_invokation_type; + + lambda_information.content_method_key = InterpreterUtil.makeUniqueKey(lambda_information.content_method_name, lambda_information.content_method_descriptor); + + anonimousClassType = new VarType(lambda_class_name, true); + + boolean is_method_reference = (content_class_name != classStruct.qualifiedName); + StructMethod mt = null; + + if(!is_method_reference) { // content method in the same class, check synthetic flag + mt = classStruct.getMethod(content_method_name, content_method_descriptor); + is_method_reference = !((mt.getAccessFlags() & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic")); // if not synthetic -> method reference + } + + lambda_information.is_method_reference = is_method_reference; + lambda_information.is_content_method_static = (lambda_information.content_method_invokation_type == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic); // FIXME: redundant? + } + + public ClassNode(int type, StructClass classStruct) { + this.type = type; + this.classStruct = classStruct; + + simpleName = classStruct.qualifiedName.substring(classStruct.qualifiedName.lastIndexOf('/')+1); + } + + public ClassNode getClassNode(String qualifiedName) { + for(ClassNode node : nested) { + if(qualifiedName.equals(node.classStruct.qualifiedName)) { + return node; + } + } + return null; + } + + public class LambdaInformation { + + + + public String class_name; + public String method_name; + public String method_descriptor; + + public String content_class_name; + public String content_method_name; + public String content_method_descriptor; + public int content_method_invokation_type; // values from CONSTANT_MethodHandle_REF_* + + public String content_method_key; + + public boolean is_method_reference; + public boolean is_content_method_static; + } + } +} diff --git a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java new file mode 100644 index 0000000..fc721b7 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -0,0 +1,197 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.util.HashMap; + +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.ImportCollector; +import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; +import org.jetbrains.java.decompiler.struct.StructContext; + + +public class DecompilerContext { + + public static final String CURRENT_CLASS = "CURRENT_CLASS"; + public static final String CURRENT_METHOD = "CURRENT_METHOD"; + public static final String CURRENT_METHOD_DESCRIPTOR = "CURRENT_METHOD_DESCRIPTOR"; + public static final String CURRENT_VAR_PROCESSOR = "CURRENT_VAR_PROCESSOR"; + + public static final String CURRENT_CLASSNODE = "CURRENT_CLASSNODE"; + public static final String CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER"; + + private static ThreadLocal<DecompilerContext> currentContext = new ThreadLocal<DecompilerContext>(); + + private HashMap<String, Object> properties = new HashMap<String, Object>(); + + private StructContext structcontext; + + private ImportCollector impcollector; + + private VarNamesCollector varncollector; + + private CounterContainer countercontainer; + + private ClassesProcessor classprocessor; + + private PoolInterceptor poolinterceptor; + + private IFernflowerLogger logger; + + + private DecompilerContext(HashMap<String, Object> properties) { + this.properties.putAll(properties); + } + + public static void initContext(HashMap<String, Object> propertiesCustom) { + + HashMap<String, Object> mapDefault = new HashMap<String, Object>(); + + // default settings + mapDefault.put(IFernflowerPreferences.DECOMPILE_INNER, "1"); + mapDefault.put(IFernflowerPreferences.DECOMPILE_CLASS_1_4, "1"); + mapDefault.put(IFernflowerPreferences.DECOMPILE_ASSERTIONS, "1"); + mapDefault.put(IFernflowerPreferences.REMOVE_BRIDGE, "1"); + mapDefault.put(IFernflowerPreferences.REMOVE_SYNTHETIC, "0"); + mapDefault.put(IFernflowerPreferences.HIDE_EMPTY_SUPER, "1"); + mapDefault.put(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR, "1"); + mapDefault.put(IFernflowerPreferences.DECOMPILE_GENERIC_SIGNATURES, "0"); + mapDefault.put(IFernflowerPreferences.OUTPUT_COPYRIGHT_COMMENT, "0"); + mapDefault.put(IFernflowerPreferences.NO_EXCEPTIONS_RETURN, "1"); + mapDefault.put(IFernflowerPreferences.DECOMPILE_ENUM, "1"); + mapDefault.put(IFernflowerPreferences.FINALLY_DEINLINE, "1"); + mapDefault.put(IFernflowerPreferences.REMOVE_GETCLASS_NEW, "1"); + mapDefault.put(IFernflowerPreferences.LITERALS_AS_IS, "0"); + mapDefault.put(IFernflowerPreferences.ASCII_STRING_CHARACTERS, "0"); + mapDefault.put(IFernflowerPreferences.BOOLEAN_TRUE_ONE, "1"); + mapDefault.put(IFernflowerPreferences.SYNTHETIC_NOT_SET, "1"); + mapDefault.put(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT, "1"); + + mapDefault.put(IFernflowerPreferences.USE_DEBUG_VARNAMES, "1"); + mapDefault.put(IFernflowerPreferences.MAX_PROCESSING_METHOD, "0"); + + mapDefault.put(IFernflowerPreferences.REMOVE_EMPTY_RANGES, "1"); + + mapDefault.put(IFernflowerPreferences.NEW_LINE_SEPARATOR, "0"); + mapDefault.put(IFernflowerPreferences.INDENT_STRING, " "); + + mapDefault.put(IFernflowerPreferences.IDEA_NOT_NULL_ANNOTATION, "1"); + + if(propertiesCustom != null) { + mapDefault.putAll(propertiesCustom); + } + + currentContext.set(new DecompilerContext(mapDefault)); + } + + public static DecompilerContext getCurrentContext() { + return currentContext.get(); + } + + public static void setCurrentContext(DecompilerContext context) { + currentContext.set(context); + } + + public static Object getProperty(String key) { + return getCurrentContext().properties.get(key); + } + + public static void setProperty(String key, Object value) { + getCurrentContext().properties.put(key, value); + } + + public static boolean getOption(String key) { + return "1".equals(getCurrentContext().properties.get(key)); + } + + public static ImportCollector getImpcollector() { + return getCurrentContext().impcollector; + } + + public static void setImpcollector(ImportCollector impcollector) { + getCurrentContext().impcollector = impcollector; + } + + public static VarNamesCollector getVarncollector() { + return getCurrentContext().varncollector; + } + + public static void setVarncollector(VarNamesCollector varncollector) { + getCurrentContext().varncollector = varncollector; + } + + public static StructContext getStructcontext() { + return getCurrentContext().structcontext; + } + + public static void setStructcontext(StructContext structcontext) { + getCurrentContext().structcontext = structcontext; + } + + public static CounterContainer getCountercontainer() { + return getCurrentContext().countercontainer; + } + + public static void setCountercontainer(CounterContainer countercontainer) { + getCurrentContext().countercontainer = countercontainer; + } + + public static ClassesProcessor getClassprocessor() { + return getCurrentContext().classprocessor; + } + + public static void setClassprocessor(ClassesProcessor classprocessor) { + getCurrentContext().classprocessor = classprocessor; + } + + public static PoolInterceptor getPoolInterceptor() { + return getCurrentContext().poolinterceptor; + } + + public static void setPoolInterceptor(PoolInterceptor poolinterceptor) { + getCurrentContext().poolinterceptor = poolinterceptor; + } + + public static IFernflowerLogger getLogger() { + return getCurrentContext().logger; + } + + public static void setLogger(IFernflowerLogger logger) { + getCurrentContext().logger = logger; + setLogSeverity(); + } + + private static void setLogSeverity() { + IFernflowerLogger logger = getCurrentContext().logger; + + if(logger != null) { + String severity = (String)getProperty(IFernflowerPreferences.LOG_LEVEL); + if(severity != null) { + Integer iSeverity = IFernflowerLogger.mapLogLevel.get(severity.toUpperCase()); + if(iSeverity != null) { + logger.setSeverity(iSeverity); + } + } + } + } + + public static String getNewLineSeparator() { + return getOption(IFernflowerPreferences.NEW_LINE_SEPARATOR) ? + IFernflowerPreferences.LINE_SEPARATOR_LIN : IFernflowerPreferences.LINE_SEPARATOR_WIN ; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/EnumProcessor.java b/src/org/jetbrains/java/decompiler/main/EnumProcessor.java new file mode 100644 index 0000000..7537596 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/EnumProcessor.java @@ -0,0 +1,159 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.FieldDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class EnumProcessor { + + public static void clearEnum(ClassWrapper wrapper) { + + StructClass cl = wrapper.getClassStruct(); + + // hide values() and valueOf() + for(StructMethod meth : cl.getMethods()) { + + String name = meth.getName(); + int flag = 0; + + if("values".equals(name)) { + flag = 1; + } else if("valueOf".equals(name)) { + flag = 2; + } + + if(flag>0) { + String[] arr = meth.getDescriptor().split("[()]"); + String par = arr[1]; + + if((flag == 1 && par.length() == 0) || + flag == 2 && "Ljava/lang/String;".equals(par)) { + wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(name, meth.getDescriptor())); + } + } + } + + // hide all super invocations + for(MethodWrapper meth : wrapper.getMethods()) { + if("<init>".equals(meth.methodStruct.getName())) { + Statement firstdata = findFirstData(meth.root); + if(firstdata == null || firstdata.getExprents().isEmpty()) { + return; + } + + Exprent exprent = firstdata.getExprents().get(0); + if(exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent)exprent; + if(isInvocationSuperConstructor(invexpr, meth, wrapper)) { + firstdata.getExprents().remove(0); + } + } + } + } + + // hide dummy synthetic fields of enum constants + for(StructField fd: cl.getFields()) { + if((fd.access_flags & CodeConstants.ACC_ENUM) != 0) { + Exprent initializer = wrapper.getStaticFieldInitializers().getWithKey(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + if(initializer != null && initializer.type == Exprent.EXPRENT_NEW) { + NewExprent nexpr = (NewExprent)initializer; + if(nexpr.isAnonymous()) { + ClassNode child = DecompilerContext.getClassprocessor().getMapRootClasses().get(nexpr.getNewtype().value); + hideDummyFieldInConstant(child.wrapper); + } + } + } + } + + + + } + + private static void hideDummyFieldInConstant(ClassWrapper wrapper) { + + StructClass cl = wrapper.getClassStruct(); + for(StructField fd: cl.getFields()) { + if((fd.access_flags & CodeConstants.ACC_SYNTHETIC) != 0) { + FieldDescriptor descr = FieldDescriptor.parseDescriptor(fd.getDescriptor()); + VarType ret = descr.type; + + if(ret.type == CodeConstants.TYPE_OBJECT && ret.arraydim == 1 && cl.qualifiedName.equals(ret.value)) { + wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } + } + } + + } + + // FIXME: move to a util class (see also InitializerProcessor) + private static Statement findFirstData(Statement stat) { + + if(stat.getExprents() != null) { + return stat; + } else { + if(stat.isLabeled()) { + return null; + } + + switch(stat.type) { + case Statement.TYPE_SEQUENCE: + case Statement.TYPE_IF: + case Statement.TYPE_ROOT: + case Statement.TYPE_SWITCH: + case Statement.TYPE_SYNCRONIZED: + return findFirstData(stat.getFirst()); + default: + return null; + } + } + } + + // FIXME: move to util class (see also InitializerProcessor) + private static boolean isInvocationSuperConstructor(InvocationExprent inv, MethodWrapper meth, ClassWrapper wrapper) { + + if(inv.getFunctype() == InvocationExprent.TYP_INIT) { + if(inv.getInstance().type == Exprent.EXPRENT_VAR) { + VarExprent instvar = (VarExprent)inv.getInstance(); + VarVersionPaar varpaar = new VarVersionPaar(instvar); + + String classname = meth.varproc.getThisvars().get(varpaar); + + if(classname!=null) { // any this instance. TODO: Restrict to current class? + if(!wrapper.getClassStruct().qualifiedName.equals(inv.getClassname())) { + return true; + } + } + } + } + + return false; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java new file mode 100644 index 0000000..021ab1a --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -0,0 +1,110 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.io.BufferedWriter; +import java.io.StringWriter; +import java.util.HashMap; + +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IDecompilatSaver; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.renamer.IdentifierConverter; +import org.jetbrains.java.decompiler.struct.IDecompiledData; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructContext; +import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; + + +public class Fernflower implements IDecompiledData { + + public static final String version = "v0.8.4"; + + private StructContext structcontext; + + private ClassesProcessor clprocessor; + + public Fernflower(IBytecodeProvider provider, IDecompilatSaver saver, + HashMap<String, Object> propertiesCustom) { + + StructContext context = new StructContext(saver, this, new LazyLoader(provider)); + + structcontext = context; + + DecompilerContext.initContext(propertiesCustom); + DecompilerContext.setCountercontainer(new CounterContainer()); + + } + + public void decompileContext() { + + if(DecompilerContext.getOption(IFernflowerPreferences.RENAME_ENTITIES)) { + IdentifierConverter ren = new IdentifierConverter(); + ren.rename(structcontext); + ren = null; + } + + clprocessor = new ClassesProcessor(structcontext); + + DecompilerContext.setClassprocessor(clprocessor); + DecompilerContext.setStructcontext(structcontext); + + structcontext.saveContext(); + } + + public void clearContext() { + DecompilerContext.setCurrentContext(null); + } + + public String getClassEntryName(StructClass cl, String entryname) { + + ClassNode node = clprocessor.getMapRootClasses().get(cl.qualifiedName); + if(node.type != ClassNode.CLASS_ROOT) { + return null; + } else { + if(DecompilerContext.getOption(IFernflowerPreferences.RENAME_ENTITIES)) { + String simple_classname = cl.qualifiedName.substring(cl.qualifiedName.lastIndexOf('/')+1); + return entryname.substring(0, entryname.lastIndexOf('/')+1)+simple_classname+".java"; + } else { + return entryname.substring(0, entryname.lastIndexOf(".class"))+".java"; + } + } + } + + public StructContext getStructcontext() { + return structcontext; + } + + public String getClassContent(StructClass cl) { + + String res = null; + + try { + StringWriter strwriter = new StringWriter(); + clprocessor.writeClass(structcontext, cl, new BufferedWriter(strwriter)); + + res = strwriter.toString(); + } catch(ThreadDeath ex) { + throw ex; + } catch(Throwable ex) { + DecompilerContext.getLogger().writeMessage("Class "+cl.qualifiedName+" couldn't be fully decompiled.", ex); + } + + return res; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java new file mode 100644 index 0000000..ff6233c --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -0,0 +1,321 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main; + +import java.util.ArrayList; +import java.util.List; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.main.rels.ClassWrapper; +import org.jetbrains.java.decompiler.main.rels.MethodWrapper; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + + +public class InitializerProcessor { + + public static void extractInitializers(ClassWrapper wrapper) { + + MethodWrapper meth = wrapper.getMethodWrapper("<clinit>", "()V"); + if(meth != null && meth.root != null) { // successfully decompiled static constructor + extractStaticInitializers(wrapper, meth); + } + + extractDynamicInitializers(wrapper); + + // required e.g. if anonymous class is being decompiled as a standard one. + // This can happen if InnerClasses attributes are erased + liftConstructor(wrapper); + + if(DecompilerContext.getOption(IFernflowerPreferences.HIDE_EMPTY_SUPER)) { + hideEmptySuper(wrapper); + } + } + + + private static void liftConstructor(ClassWrapper wrapper) { + + for(MethodWrapper meth : wrapper.getMethods()) { + if("<init>".equals(meth.methodStruct.getName()) && meth.root != null) { + Statement firstdata = findFirstData(meth.root); + if(firstdata == null) { + return; + } + + + int index = 0; + List<Exprent> lstExprents = firstdata.getExprents(); + + for(Exprent exprent : lstExprents) { + + int action = 0; + + if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent asexpr = (AssignmentExprent)exprent; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD && asexpr.getRight().type == Exprent.EXPRENT_VAR) { + FieldExprent fexpr = (FieldExprent)asexpr.getLeft(); + if(fexpr.getClassname().equals(wrapper.getClassStruct().qualifiedName)) { + StructField structField = wrapper.getClassStruct().getField(fexpr.getName(), fexpr.getDescriptor().descriptorString); + if(structField != null && (structField.access_flags & CodeConstants.ACC_FINAL) != 0) { + action = 1; + } + } + } + } else if(index > 0 && exprent.type == Exprent.EXPRENT_INVOCATION && + isInvocationInitConstructor((InvocationExprent)exprent, meth, wrapper, true)) { + // this() or super() + lstExprents.add(0, lstExprents.remove(index)); + action = 2; + } + + if(action != 1) { + break; + } + + index++; + } + } + } + } + + + private static void hideEmptySuper(ClassWrapper wrapper) { + + for(MethodWrapper meth : wrapper.getMethods()) { + if("<init>".equals(meth.methodStruct.getName()) && meth.root != null) { + Statement firstdata = findFirstData(meth.root); + if(firstdata == null || firstdata.getExprents().isEmpty()) { + return; + } + + Exprent exprent = firstdata.getExprents().get(0); + if(exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent)exprent; + if(isInvocationInitConstructor(invexpr, meth, wrapper, false) && invexpr.getLstParameters().isEmpty()) { + firstdata.getExprents().remove(0); + } + } + } + } + } + + private static void extractStaticInitializers(ClassWrapper wrapper, MethodWrapper meth) { + + RootStatement root = meth.root; + StructClass cl = wrapper.getClassStruct(); + + Statement firstdata = findFirstData(root); + if(firstdata != null) { + while(!firstdata.getExprents().isEmpty()) { + Exprent exprent = firstdata.getExprents().get(0); + + boolean found = false; + + if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent asexpr = (AssignmentExprent)exprent; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) { + FieldExprent fexpr = (FieldExprent)asexpr.getLeft(); + if(fexpr.isStatic() && fexpr.getClassname().equals(cl.qualifiedName) && + cl.hasField(fexpr.getName(), fexpr.getDescriptor().descriptorString)) { + + if(isExprentIndependent(asexpr.getRight(), meth)) { + + String keyField = InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString); + if(!wrapper.getStaticFieldInitializers().containsKey(keyField)) { + wrapper.getStaticFieldInitializers().addWithKey(asexpr.getRight(), keyField); + firstdata.getExprents().remove(0); + found = true; + } + } + } + } + } + + if(!found) { + break; + } + } + } + } + + private static void extractDynamicInitializers(ClassWrapper wrapper) { + + StructClass cl = wrapper.getClassStruct(); + + boolean isAnonymous = DecompilerContext.getClassprocessor().getMapRootClasses().get(cl.qualifiedName).type == ClassNode.CLASS_ANONYMOUS; + + List<List<Exprent>> lstFirst = new ArrayList<List<Exprent>>(); + List<MethodWrapper> lstMethWrappers = new ArrayList<MethodWrapper>(); + + for(MethodWrapper meth : wrapper.getMethods()) { + if("<init>".equals(meth.methodStruct.getName()) && meth.root != null) { // successfully decompiled constructor + Statement firstdata = findFirstData(meth.root); + if(firstdata == null || firstdata.getExprents().isEmpty()) { + return; + } + lstFirst.add(firstdata.getExprents()); + lstMethWrappers.add(meth); + + Exprent exprent = firstdata.getExprents().get(0); + if(!isAnonymous) { // FIXME: doesn't make sense + if(exprent.type != Exprent.EXPRENT_INVOCATION || !isInvocationInitConstructor((InvocationExprent)exprent, meth, wrapper, false)) { + return; + } + } + } + } + + if(lstFirst.isEmpty()) { + return; + } + + for(;;) { + + String fieldWithDescr = null; + Exprent value = null; + + for(int i=0; i<lstFirst.size(); i++) { + + List<Exprent> lst = lstFirst.get(i); + + if(lst.size() < (isAnonymous?1:2)) { + return; + } + + Exprent exprent = lst.get(isAnonymous?0:1); + + boolean found = false; + + if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent asexpr = (AssignmentExprent)exprent; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) { + FieldExprent fexpr = (FieldExprent)asexpr.getLeft(); + if(!fexpr.isStatic() && fexpr.getClassname().equals(cl.qualifiedName) && + cl.hasField(fexpr.getName(), fexpr.getDescriptor().descriptorString)) { // check for the physical existence of the field. Could be defined in a superclass. + + if(isExprentIndependent(asexpr.getRight(), lstMethWrappers.get(i))) { + String fieldKey = InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString); + if(fieldWithDescr == null) { + fieldWithDescr = fieldKey; + value = asexpr.getRight(); + } else { + if(!fieldWithDescr.equals(fieldKey) || + !value.equals(asexpr.getRight())) { + return; + } + } + found = true; + } + } + } + } + + if(!found) { + return; + } + } + + if(!wrapper.getDynamicFieldInitializers().containsKey(fieldWithDescr)) { + wrapper.getDynamicFieldInitializers().addWithKey(value, fieldWithDescr); + + for(List<Exprent> lst : lstFirst) { + lst.remove(isAnonymous?0:1); + } + } else { + return; + } + } + + } + + private static boolean isExprentIndependent(Exprent exprent, MethodWrapper meth) { + + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + for(Exprent expr : lst) { + switch(expr.type) { + case Exprent.EXPRENT_VAR: + VarVersionPaar varpaar = new VarVersionPaar((VarExprent)expr); + if(!meth.varproc.getExternvars().contains(varpaar)) { + String varname = meth.varproc.getVarName(varpaar); + + if(!varname.equals("this") && !varname.endsWith(".this")) { // FIXME: remove direct comparison with strings + return false; + } + } + break; + case Exprent.EXPRENT_FIELD: + return false; + } + } + + return true; + } + + + private static Statement findFirstData(Statement stat) { + + if(stat.getExprents() != null) { + return stat; + } else { + if(stat.isLabeled()) { // FIXME: Why?? + return null; + } + + switch(stat.type) { + case Statement.TYPE_SEQUENCE: + case Statement.TYPE_IF: + case Statement.TYPE_ROOT: + case Statement.TYPE_SWITCH: + case Statement.TYPE_SYNCRONIZED: + return findFirstData(stat.getFirst()); + default: + return null; + } + } + } + + private static boolean isInvocationInitConstructor(InvocationExprent inv, MethodWrapper meth, ClassWrapper wrapper, boolean withThis) { + + if(inv.getFunctype() == InvocationExprent.TYP_INIT) { + if(inv.getInstance().type == Exprent.EXPRENT_VAR) { + VarExprent instvar = (VarExprent)inv.getInstance(); + VarVersionPaar varpaar = new VarVersionPaar(instvar); + + String classname = meth.varproc.getThisvars().get(varpaar); + + if(classname!=null) { // any this instance. TODO: Restrict to current class? + if(withThis || !wrapper.getClassStruct().qualifiedName.equals(inv.getClassname())) { + return true; + } + } + } + } + + return false; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java b/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java new file mode 100644 index 0000000..dcc1968 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java @@ -0,0 +1,37 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.collectors; + +public class CounterContainer { + + public static final int STATEMENT_COUNTER = 0; + public static final int EXPRENT_COUNTER = 1; + public static final int VAR_COUNTER = 2; + + private int[] values = new int[]{1, 1, 1}; + + public void setCounter(int counter, int value) { + values[counter] = value; + } + + public int getCounter(int counter) { + return values[counter]; + } + + public int getCounterAndIncrement(int counter) { + return values[counter]++; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java new file mode 100644 index 0000000..cdc6529 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java @@ -0,0 +1,157 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.collectors; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; + +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.struct.StructContext; + + +public class ImportCollector { + + private static final String JAVA_LANG_PACKAGE = "java.lang"; + + private HashMap<String, String> mapSimpleNames = new HashMap<String, String>(); + + private HashSet<String> setNotImportedNames = new HashSet<String>(); + + private String currentPackageSlash = ""; + + private String currentPackagePoint = ""; + + public ImportCollector(ClassNode root) { + + String clname = root.classStruct.qualifiedName; + int index = clname.lastIndexOf("/"); + if(index >= 0) { + currentPackageSlash = clname.substring(0, index); + currentPackagePoint = currentPackageSlash.replace('/', '.'); + currentPackageSlash += "/"; + } + } + + public String getShortName(String fullname) { + return getShortName(fullname, true); + } + + public String getShortName(String fullname, boolean imported) { + + ClassesProcessor clproc = DecompilerContext.getClassprocessor(); + ClassNode node = clproc.getMapRootClasses().get(fullname.replace('.', '/')); + + String retname = null; + + if(node != null && node.classStruct.isOwn()) { + + retname = node.simpleName; + + while(node.parent != null && node.type == ClassNode.CLASS_MEMBER) { + retname = node.parent.simpleName+"."+retname; + node = node.parent; + } + + if(node.type == ClassNode.CLASS_ROOT) { + fullname = node.classStruct.qualifiedName; + fullname = fullname.replace('/', '.'); + } else { + return retname; + } + + } else if(node == null || !node.classStruct.isOwn()) { + fullname = fullname.replace('$', '.'); + } + + String nshort = fullname; + String npackage = ""; + + int lastpoint = fullname.lastIndexOf("."); + + if(lastpoint >= 0) { + nshort = fullname.substring(lastpoint+1); + npackage = fullname.substring(0, lastpoint); + } + + StructContext context = DecompilerContext.getStructcontext(); + + boolean existsDefaultClass = (context.getClass(currentPackageSlash+nshort) != null + && !npackage.equals(currentPackagePoint)) // current package + || (context.getClass(nshort) != null); // default package + + if(existsDefaultClass || + (mapSimpleNames.containsKey(nshort) && !npackage.equals(mapSimpleNames.get(nshort)))) { + return fullname; + } else if(!mapSimpleNames.containsKey(nshort)) { + mapSimpleNames.put(nshort, npackage); + + if(!imported) { + setNotImportedNames.add(nshort); + } + } + + return retname==null?nshort:retname; + } + + public void writeImports(BufferedWriter writer) throws IOException { + + for(String s: packImports()) { + writer.write("import "); + writer.write(s); + writer.write(";"); + writer.write(DecompilerContext.getNewLineSeparator()); + } + + } + + private List<String> packImports() { + + List<Entry<String, String>> lst = new ArrayList<Entry<String, String>>(mapSimpleNames.entrySet()); + + Collections.sort(lst, new Comparator<Entry<String, String>>() { + public int compare(Entry<String, String> par0, Entry<String, String> par1) { + int res = par0.getValue().compareTo(par1.getValue()); + if(res == 0) { + res = par0.getKey().compareTo(par1.getKey()); + } + return res; + } + }); + + List<String> res = new ArrayList<String>(); + for(Entry<String, String> ent: lst) { + if(!setNotImportedNames.contains(ent.getKey()) // not the current class or one of the nested ones. Also not the empty package. + && !JAVA_LANG_PACKAGE.equals(ent.getValue()) + && ent.getValue().length() > 0) { + + String imp = ent.getValue()+"."+ent.getKey(); + res.add(imp); + } + } + + return res; + } + + +} diff --git a/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java b/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java new file mode 100644 index 0000000..5fd33c0 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java @@ -0,0 +1,50 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.collectors; + +import java.util.HashSet; + +public class VarNamesCollector { + + private HashSet<String> usedNames = new HashSet<String>(); + + public VarNamesCollector() {} + + public VarNamesCollector(HashSet<String> setNames) { + usedNames.addAll(setNames); + } + + public void addName(String value) { + usedNames.add(value); + } + + public String getFreeName(int index) { + return getFreeName("var"+index); + } + + public String getFreeName(String proposition) { + + while(usedNames.contains(proposition)) { + proposition+="x"; + } + usedNames.add(proposition); + return proposition; + } + + public HashSet<String> getUsedNames() { + return usedNames; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java new file mode 100644 index 0000000..408c430 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -0,0 +1,323 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.decompiler; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.decompiler.helper.PrintStreamLogger; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IDecompilatSaver; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + + +public class ConsoleDecompiler implements IBytecodeProvider, IDecompilatSaver { + + private File root; + + private Fernflower fernflower; + + private HashMap<String, ZipOutputStream> mapArchiveStreams = new HashMap<String, ZipOutputStream>(); + + private HashMap<String, HashSet<String>> mapArchiveEntries = new HashMap<String, HashSet<String>>(); + + public ConsoleDecompiler() { + this(null); + } + + public ConsoleDecompiler(HashMap<String, Object> propertiesCustom) { + this(new PrintStreamLogger(IFernflowerLogger.WARNING, System.out), propertiesCustom); + } + + protected ConsoleDecompiler(IFernflowerLogger logger, HashMap<String, Object> propertiesCustom) { + fernflower = new Fernflower(this, this, propertiesCustom); + DecompilerContext.setLogger(logger); + } + + public static void main(String[] args) { + + try { + + if(args != null && args.length > 1) { + + HashMap<String, Object> mapOptions = new HashMap<String, Object>(); + + List<String> lstSources = new ArrayList<String>(); + List<String> lstLibraries = new ArrayList<String>(); + + boolean isOption = true; + for(int i = 0; i < args.length - 1; ++i) { // last parameter - destination + String arg = args[i]; + + if(isOption && arg.startsWith("-") && + arg.length()>5 && arg.charAt(4) == '=') { + String value = arg.substring(5).toUpperCase(); + if("TRUE".equals(value)) { + value = "1"; + } else if("FALSE".equals(value)) { + value = "0"; + } + + mapOptions.put(arg.substring(1, 4), value); + } else { + isOption = false; + + if(arg.startsWith("-e=")) { + lstLibraries.add(arg.substring(3)); + } else { + lstSources.add(arg); + } + } + } + + if(lstSources.isEmpty()) { + printHelp(); + } else { + ConsoleDecompiler decompiler = new ConsoleDecompiler( + new PrintStreamLogger(IFernflowerLogger.INFO, System.out), + mapOptions); + + for(String source : lstSources) { + decompiler.addSpace(new File(source), true); + } + + for(String library : lstLibraries) { + decompiler.addSpace(new File(library), false); + } + + decompiler.decompileContext(new File(args[args.length-1])); + } + + } else { + printHelp(); + } + + } catch(Exception ex) { + ex.printStackTrace(); + } + + } + + private static void printHelp() { + System.out.println("Usage: java ConsoleDecompiler ( -<option>=<value>)* (<source>)+ <destination>"); + System.out.println("Example: java ConsoleDecompiler -dgs=true c:\\mysource\\ c:\\my.jar d:\\decompiled\\"); + } + + public void addSpace(File file, boolean isOwn) throws IOException { + fernflower.getStructcontext().addSpace(file, isOwn); + } + + public void decompileContext(File root) { + this.root = root; + fernflower.decompileContext(); + } + + // ******************************************************************* + // Interface IBytecodeProvider + // ******************************************************************* + + public InputStream getBytecodeStream(String externPath, String internPath) { + + try { + File file = new File(externPath); + + if(internPath == null) { + return new FileInputStream(file); + } else { // archive file + ZipFile archive = new ZipFile(file); + + Enumeration<? extends ZipEntry> en = archive.entries(); + while(en.hasMoreElements()) { + ZipEntry entr = en.nextElement(); + + if(entr.getName().equals(internPath)) { + return archive.getInputStream(entr); + } + } + } + } catch(IOException ex) { + ex.printStackTrace(); + } + + return null; + } + + // ******************************************************************* + // Interface IDecompilatSaver + // ******************************************************************* + + private String getAbsolutePath(String path) { + return new File(root, path).getAbsolutePath(); + } + + private boolean addEntryName(String filename, String entry) { + HashSet<String> set = mapArchiveEntries.get(filename); + if(set == null) { + mapArchiveEntries.put(filename, set = new HashSet<String>()); + } + + return set.add(entry); + } + + public void copyEntry(String source, String destpath, String archivename, String entryName) { + + try { + String filename = new File(getAbsolutePath(destpath), archivename).getAbsolutePath(); + + if(!addEntryName(filename, entryName)) { + DecompilerContext.getLogger().writeMessage("Zip entry already exists: "+ + destpath+","+archivename+","+entryName, IFernflowerLogger.WARNING); + return; + } + + ZipFile srcarchive = new ZipFile(new File(source)); + + Enumeration<? extends ZipEntry> en = srcarchive.entries(); + while(en.hasMoreElements()) { + ZipEntry entr = en.nextElement(); + + if(entr.getName().equals(entryName)) { + InputStream in = srcarchive.getInputStream(entr); + + ZipOutputStream out = mapArchiveStreams.get(filename); + out.putNextEntry(new ZipEntry(entryName)); + + InterpreterUtil.copyInputStream(in, out); + in.close(); + } + } + + srcarchive.close(); + + } catch(IOException ex) { + DecompilerContext.getLogger().writeMessage("Error copying zip file entry: "+source+","+destpath+","+archivename+","+entryName, IFernflowerLogger.WARNING); + ex.printStackTrace(); + } + } + + public void copyFile(String source, String destpath, String destfilename) { + try { + InterpreterUtil.copyFile(new File(source), new File(destfilename)); + } catch(IOException ex) { + ex.printStackTrace(); + } + } + + public void saveFile(String path, String filename, String content) { + try { + BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(getAbsolutePath(path), filename)), "UTF8")); + out.write(content); + out.flush(); + out.close(); + } catch(IOException ex) { + ex.printStackTrace(); + } + } + + public void createArchive(String path, String archivename, Manifest manifest) { + + try { + File file = new File(getAbsolutePath(path), archivename); + file.createNewFile(); + + ZipOutputStream out; + if(manifest != null) { // jar + out = new JarOutputStream(new FileOutputStream(file), manifest); + } else { + out = new ZipOutputStream(new FileOutputStream(file)); + } + mapArchiveStreams.put(file.getAbsolutePath(), out); + + } catch(IOException ex) { + ex.printStackTrace(); + } + } + + public void saveClassEntry(String path, String archivename, + String qualifiedName, String entryName, String content) { + saveEntry(path, archivename, entryName, content); + } + + public void saveClassFile(String path, String qualifiedName, String entryName, String content) { + saveFile(path, entryName, content); + } + + public void saveEntry(String path, String archivename, String entryName, + String content) { + + try { + String filename = new File(getAbsolutePath(path), archivename).getAbsolutePath(); + + if(!addEntryName(filename, entryName)) { + DecompilerContext.getLogger().writeMessage("Zip entry already exists: "+ + path+","+archivename+","+entryName, IFernflowerLogger.WARNING); + return; + } + + ZipOutputStream out = mapArchiveStreams.get(filename); + out.putNextEntry(new ZipEntry(entryName)); + + if(content != null) { + BufferedWriter outwriter = new BufferedWriter(new OutputStreamWriter(out, "UTF8")); + outwriter.write(content); + outwriter.flush(); + } + + } catch(IOException ex) { + ex.printStackTrace(); + } + } + + public void saveFolder(String path) { + File f = new File(getAbsolutePath(path)); + f.mkdirs(); + } + + + public void closeArchive(String path, String archivename) { + try { + String filename = new File(getAbsolutePath(path), archivename).getAbsolutePath(); + + mapArchiveEntries.remove(filename); + ZipOutputStream out = mapArchiveStreams.remove(filename); + + out.flush(); + out.close(); + + } catch(IOException ex) { + ex.printStackTrace(); + } + } + + +} diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/IdeDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/IdeDecompiler.java new file mode 100644 index 0000000..07dd0f0 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/IdeDecompiler.java @@ -0,0 +1,54 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.decompiler; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; + +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IDecompilatSaver; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; + + +public class IdeDecompiler { + + private Fernflower fernflower; + + public IdeDecompiler(IBytecodeProvider provider, + IDecompilatSaver saver, IFernflowerLogger logger, + HashMap<String, Object> propertiesCustom) { + + fernflower = new Fernflower(provider, saver, propertiesCustom); + + DecompilerContext.setLogger(logger); + + } + + public void addSpace(File file, boolean isOwn) throws IOException { + fernflower.getStructcontext().addSpace(file, isOwn); + } + + public void decompileContext() { + try { + fernflower.decompileContext(); + } finally { + fernflower.clearContext(); + } + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/WebDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/WebDecompiler.java new file mode 100644 index 0000000..5f20c01 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/WebDecompiler.java @@ -0,0 +1,78 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.decompiler; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; + + +public class WebDecompiler extends ConsoleDecompiler { + + private HashMap<String, File> mapInputFilenames = new HashMap<String, File>(); + + private HashSet<String> setClassFiles = new HashSet<String>(); + + private File root; + + public WebDecompiler(IFernflowerLogger logger, HashMap<String, Object> propertiesCustom) { + super(logger, propertiesCustom); + } + + @Override + public void decompileContext(File root) { + this.root = root; + super.decompileContext(root); + } + + @Override + public void copyFile(String source, String destpath, String destfilename) { + super.copyFile(source, destpath, destfilename); + mapInputFilenames.put(destfilename, new File(getAbsolutePath(destpath), destfilename)); + } + + @Override + public void saveFile(String path, String filename, String content) { + super.saveFile(path, filename, content); + + mapInputFilenames.put(setClassFiles.contains(filename)? + filename.substring(0, filename.lastIndexOf(".java"))+".class": + filename, new File(getAbsolutePath(path), filename)); + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content) { + setClassFiles.add(entryName); + saveFile(path, entryName, content); + } + + @Override + public void closeArchive(String path, String archivename) { + super.closeArchive(path, archivename); + mapInputFilenames.put(archivename, new File(getAbsolutePath(path), archivename)); + } + + private String getAbsolutePath(String path) { + return new File(root, path).getAbsolutePath(); + } + + public HashMap<String, File> getMapInputFilenames() { + return mapInputFilenames; + } + +} + diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/helper/PrintStreamLogger.java b/src/org/jetbrains/java/decompiler/main/decompiler/helper/PrintStreamLogger.java new file mode 100644 index 0000000..6d03b34 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/decompiler/helper/PrintStreamLogger.java @@ -0,0 +1,83 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.decompiler.helper; + +import java.io.PrintStream; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class PrintStreamLogger implements IFernflowerLogger { + + private int severity; + + private int indent; + + private PrintStream stream; + + public PrintStreamLogger(int severity, PrintStream stream) { + this.severity = severity; + this.indent = 0; + this.stream = stream; + } + + + public void writeMessage(String message, int severity) { + if (severity >= this.severity) { + stream.println(InterpreterUtil.getIndentString(indent) + names[severity] + ": " + message); + } + } + + public void writeMessage(String message, Throwable t) { + t.printStackTrace(stream); + writeMessage(message, ERROR); + } + + public void startClass(String classname) { + stream.println(InterpreterUtil.getIndentString(indent++)+"Processing class "+classname+" ..."); + } + + public void endClass() { + stream.println(InterpreterUtil.getIndentString(--indent)+"... proceeded."); + } + + public void startWriteClass(String classname) { + stream.println(InterpreterUtil.getIndentString(indent++)+"Writing class "+classname+" ..."); + } + + public void endWriteClass() { + stream.println(InterpreterUtil.getIndentString(--indent)+"... written."); + } + + public void startMethod(String method) { + if(severity <= INFO) { + stream.println(InterpreterUtil.getIndentString(indent)+"Processing method "+method+" ..."); + } + } + + public void endMethod() { + if(severity <= INFO) { + stream.println(InterpreterUtil.getIndentString(indent)+"... proceeded."); + } + } + + public int getSeverity() { + return severity; + } + + public void setSeverity(int severity) { + this.severity = severity; + } +} diff --git a/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java b/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java new file mode 100644 index 0000000..6e13e51 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java @@ -0,0 +1,23 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.extern; + +import java.io.InputStream; + +public interface IBytecodeProvider { + + public InputStream getBytecodeStream(String externPath, String internPath); + +} diff --git a/src/org/jetbrains/java/decompiler/main/extern/IDecompilatSaver.java b/src/org/jetbrains/java/decompiler/main/extern/IDecompilatSaver.java new file mode 100644 index 0000000..855f95c --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/extern/IDecompilatSaver.java @@ -0,0 +1,39 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.extern; + +import java.util.jar.Manifest; + +public interface IDecompilatSaver { + + public void copyFile(String source, String destpath, String destfilename); + + public void saveFolder(String path); + + public void saveClassFile(String path, String qualifiedName, String entryName, String content); + + public void saveFile(String path, String filename, String content); + + public void createArchive(String path, String archivename, Manifest manifest); + + public void saveClassEntry(String path, String archivename, String qualifiedName, String entryName, String content); + + public void saveEntry(String path, String archivename, String entryName, String content); + + public void copyEntry(String source, String destpath, String archivename, String entry); + + public void closeArchive(String path, String archivename); + +} diff --git a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java new file mode 100644 index 0000000..eb1427c --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java @@ -0,0 +1,56 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.extern; + +import java.util.HashMap; + +public interface IFernflowerLogger { + + public static final int TRACE = 1; + public static final int INFO = 2; + public static final int WARNING = 3; + public static final int ERROR = 4; + public static final int IMMEDIATE = 5; + + public static final HashMap<String, Integer> mapLogLevel = new HashMap<String, Integer>() {{ + put("TRACE", 1); + put("INFO", 2); + put("WARN", 3); + put("ERROR", 4); + put("IMME", 5); + }}; + + public static final String[] names = new String[] {""/*DUMMY ENTRY*/, "TRACE", "INFO", "WARNING", "ERROR", ""/*IMMEDIATE*/}; + + public void writeMessage(String message, int severity); + + public void writeMessage(String message, Throwable t); + + public void startClass(String classname); + + public void endClass(); + + public void startWriteClass(String classname); + + public void endWriteClass(); + + public void startMethod(String method); + + public void endMethod(); + + public int getSeverity(); + + public void setSeverity(int severity); +} diff --git a/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java new file mode 100644 index 0000000..9f19127 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java @@ -0,0 +1,57 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.extern; + +public interface IFernflowerPreferences { + + public static final String REMOVE_BRIDGE = "rbr"; + public static final String REMOVE_SYNTHETIC = "rsy"; + public static final String DECOMPILE_INNER = "din"; + public static final String DECOMPILE_CLASS_1_4 = "dc4"; + public static final String DECOMPILE_ASSERTIONS = "das"; + public static final String HIDE_EMPTY_SUPER = "hes"; + public static final String HIDE_DEFAULT_CONSTRUCTOR = "hdc"; + public static final String DECOMPILE_GENERIC_SIGNATURES = "dgs"; + public static final String OUTPUT_COPYRIGHT_COMMENT = "occ"; + public static final String NO_EXCEPTIONS_RETURN = "ner"; + public static final String DECOMPILE_ENUM = "den"; + public static final String REMOVE_GETCLASS_NEW = "rgn"; + public static final String LITERALS_AS_IS = "lit"; + public static final String BOOLEAN_TRUE_ONE = "bto"; + public static final String SYNTHETIC_NOT_SET = "nns"; + public static final String UNDEFINED_PARAM_TYPE_OBJECT = "uto"; + public static final String USE_DEBUG_VARNAMES = "udv"; + public static final String MAX_PROCESSING_METHOD = "mpm"; + public static final String REMOVE_EMPTY_RANGES = "rer"; + public static final String ASCII_STRING_CHARACTERS = "asc"; + + public static final String FINALLY_DEINLINE = "fdi"; + + public static final String FINALLY_CATCHALL = "FINALLY_CATCHALL"; + public static final String FINALLY_SEMAPHOR = "FINALLY_SEMAPHOR"; + + public static final String RENAME_ENTITIES = "ren"; + public static final String USER_RENAMER_CLASS = "urc"; + + public static final String LOG_LEVEL = "log"; + + public static final String NEW_LINE_SEPARATOR = "nls"; + public static final String IDEA_NOT_NULL_ANNOTATION = "inn"; + public static final String LAMBDA_TO_ANONYMOUS_CLASS = "lac"; + public static final String INDENT_STRING = "ind"; + + public static final String LINE_SEPARATOR_WIN = "\r\n"; + public static final String LINE_SEPARATOR_LIN = "\n"; +} diff --git a/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java b/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java new file mode 100644 index 0000000..f379da6 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java @@ -0,0 +1,20 @@ +package org.jetbrains.java.decompiler.main.extern; + + +public interface IIdentifierRenamer { + + public static final int ELEMENT_CLASS = 1; + + public static final int ELEMENT_FIELD = 2; + + public static final int ELEMENT_METHOD = 3; + + + public boolean toBeRenamed(int element_type, String classname, String element, String descriptor); + + public String getNextClassname(String fullname, String shortname); + + public String getNextFieldname(String classname, String field, String descriptor); + + public String getNextMethodname(String classname, String method, String descriptor); +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java new file mode 100644 index 0000000..dcbeaea --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java @@ -0,0 +1,216 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.rels; + +import java.io.IOException; +import java.util.HashSet; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructLocalVariableTableAttribute; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.util.InterpreterUtil; +import org.jetbrains.java.decompiler.util.VBStyleCollection; + +public class ClassWrapper { + + private StructClass classStruct; + + private HashSet<String> hideMembers = new HashSet<String>(); + + private VBStyleCollection<Exprent, String> staticFieldInitializers = new VBStyleCollection<Exprent, String>(); + + private VBStyleCollection<Exprent, String> dynamicFieldInitializers = new VBStyleCollection<Exprent, String>(); + + private VBStyleCollection<MethodWrapper, String> methods = new VBStyleCollection<MethodWrapper, String>(); + + + public ClassWrapper(StructClass classStruct) { + this.classStruct = classStruct; + } + + @SuppressWarnings("deprecation") + public void init() throws IOException { + + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS, classStruct); + + DecompilerContext.getLogger().startClass(classStruct.qualifiedName); + + // collect field names + HashSet<String> setFieldNames = new HashSet<String>(); + for(StructField fd: classStruct.getFields()) { + setFieldNames.add(fd.getName()); + } + + for(StructMethod mt: classStruct.getMethods()) { + + DecompilerContext.getLogger().startMethod(mt.getName()+" "+mt.getDescriptor()); + + VarNamesCollector vc = new VarNamesCollector(); + DecompilerContext.setVarncollector(vc); + + CounterContainer counter = new CounterContainer(); + DecompilerContext.setCountercontainer(counter); + + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD, mt); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_DESCRIPTOR, MethodDescriptor.parseDescriptor(mt.getDescriptor())); + + VarProcessor varproc = new VarProcessor(); + DecompilerContext.setProperty(DecompilerContext.CURRENT_VAR_PROCESSOR, varproc); + + Thread mtthread = null; + RootStatement root = null; + + boolean isError = false; + + try { + if(mt.containsCode()) { + + int maxsec = 10 * Integer.parseInt(DecompilerContext.getProperty(IFernflowerPreferences.MAX_PROCESSING_METHOD).toString()); + + if(maxsec == 0) { // blocking wait + root = MethodProcessorThread.codeToJava(mt, varproc); + } else { + MethodProcessorThread mtproc = new MethodProcessorThread(mt, varproc, DecompilerContext.getCurrentContext()); + mtthread = new Thread(mtproc); + + mtthread.start(); + + int sec = 0; + while(mtthread.isAlive()) { + + synchronized(mtproc) { + mtproc.wait(100); + } + + if(maxsec > 0 && ++sec > maxsec) { + DecompilerContext.getLogger().writeMessage("Processing time limit ("+maxsec+" sec.) for method " + + mt.getName()+" "+mt.getDescriptor()+ " exceeded, execution interrupted.", IFernflowerLogger.ERROR); + mtthread.stop(); + isError = true; + break; + } + } + + if(!isError) { + if(mtproc.getError() != null) { + throw mtproc.getError(); + } else { + root = mtproc.getRoot(); + } + } + } + + } else { + boolean thisvar = (mt.getAccessFlags() & CodeConstants.ACC_STATIC) == 0; + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); + + int paramcount = 0; + if(thisvar) { + varproc.getThisvars().put(new VarVersionPaar(0,0), classStruct.qualifiedName); + paramcount = 1; + } + paramcount += md.params.length; + + int varindex = 0; + for(int i=0;i<paramcount;i++) { + varproc.setVarName(new VarVersionPaar(varindex, 0), vc.getFreeName(varindex)); + + if(thisvar) { + if(i==0) { + varindex++; + } else { + varindex+=md.params[i-1].stack_size; + } + } else { + varindex+=md.params[i].stack_size; + } + } + } + + } catch(ThreadDeath ex) { + try { + if(mtthread != null) { + mtthread.stop(); + } + } catch(Throwable ignored) { } + + throw ex; + } catch(Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method "+mt.getName()+" "+mt.getDescriptor()+" couldn't be decompiled.", ex); + isError = true; + } + + MethodWrapper meth = new MethodWrapper(root, varproc, mt, counter); + meth.decompiledWithErrors = isError; + + methods.addWithKey(meth, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + + // rename vars so that no one has the same name as a field + varproc.refreshVarNames(new VarNamesCollector(setFieldNames)); + + // if debug information present and should be used + if(DecompilerContext.getOption(IFernflowerPreferences.USE_DEBUG_VARNAMES)) { + StructLocalVariableTableAttribute attr = (StructLocalVariableTableAttribute)mt.getAttributes().getWithKey( + StructGeneralAttribute.ATTRIBUTE_LOCAL_VARIABLE_TABLE); + + if(attr != null) { + varproc.setDebugVarNames(attr.getMapVarNames()); + } + } + + DecompilerContext.getLogger().endMethod(); + } + + DecompilerContext.getLogger().endClass(); + } + + public MethodWrapper getMethodWrapper(String name, String descriptor) { + return methods.getWithKey(InterpreterUtil.makeUniqueKey(name, descriptor)); + } + + public StructClass getClassStruct() { + return classStruct; + } + + public VBStyleCollection<MethodWrapper, String> getMethods() { + return methods; + } + + public HashSet<String> getHideMembers() { + return hideMembers; + } + + public VBStyleCollection<Exprent, String> getStaticFieldInitializers() { + return staticFieldInitializers; + } + + public VBStyleCollection<Exprent, String> getDynamicFieldInitializers() { + return dynamicFieldInitializers; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java new file mode 100644 index 0000000..bf5f72f --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java @@ -0,0 +1,138 @@ +package org.jetbrains.java.decompiler.main.rels; + +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.main.ClassesProcessor; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructBootstrapMethodsAttribute; +import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; +import org.jetbrains.java.decompiler.struct.consts.LinkConstant; +import org.jetbrains.java.decompiler.struct.consts.PooledConstant; +import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +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;"; + + public void processClass(ClassNode node) throws IOException { + + for(ClassNode child : node.nested) { + processClass(child); + } + + if(node.nested.isEmpty()) { + hasLambda(node); + } + } + + public boolean hasLambda(ClassNode node) throws IOException { + + ClassesProcessor clprocessor = DecompilerContext.getClassprocessor(); + StructClass cl = node.classStruct; + + if(cl.getBytecodeVersion() < CodeConstants.BYTECODE_JAVA_8) { // lamda beginning with Java 8 + return false; + } + + StructBootstrapMethodsAttribute bootstrap = (StructBootstrapMethodsAttribute)cl.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS); + if(bootstrap == null || bootstrap.getMethodsNumber() == 0) { + return false; // no bootstrap constants in pool + } + + Set<Integer> lambda_methods = new HashSet<Integer>(); + + // find lambda bootstrap constants + for(int i = 0; i < bootstrap.getMethodsNumber(); ++i) { + LinkConstant method_ref = bootstrap.getMethodReference(i); // method handle + + 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); + } + } + + if(lambda_methods.isEmpty()) { + return false; // no lambda bootstrap constant found + } + + Map<String, String> mapMethodsLambda = new HashMap<String, String>(); + + // iterate over code and find invocations of bootstrap methods. Replace them with anonymous classes. + for(StructMethod mt: cl.getMethods()) { + mt.expandData(); + + InstructionSequence seq = mt.getInstructionSequence(); + if(seq != null && seq.length() > 0) { + int len = seq.length(); + + for(int i = 0; i < len; ++i) { + Instruction instr = seq.getInstr(i); + + 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 + + List<PooledConstant> bootstrap_arguments = bootstrap.getMethodArguments(invoke_dynamic.index1); + MethodDescriptor md = MethodDescriptor.parseDescriptor(invoke_dynamic.descriptor); + + String lambda_class_name = md.ret.value; + String lambda_method_name = invoke_dynamic.elementname; + String lambda_method_descriptor = ((PrimitiveConstant)bootstrap_arguments.get(2)).getString(); // method type + + LinkConstant content_method_handle = (LinkConstant)bootstrap_arguments.get(1); + + ClassNode node_lambda = clprocessor.new ClassNode(content_method_handle.classname, content_method_handle.elementname, + content_method_handle.descriptor, content_method_handle.index1, + lambda_class_name, lambda_method_name, lambda_method_descriptor, cl); + node_lambda.simpleName = cl.qualifiedName + "##Lambda_" + invoke_dynamic.index1 + "_" + invoke_dynamic.index2; + node_lambda.enclosingMethod = InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()); + + node.nested.add(node_lambda); + node_lambda.parent = node; + + clprocessor.getMapRootClasses().put(node_lambda.simpleName, node_lambda); + mapMethodsLambda.put(node_lambda.lambda_information.content_method_key, node_lambda.simpleName); + } + } + } + } + + mt.releaseResources(); + } + + // build class hierarchy on lambda + for(ClassNode nd : node.nested) { + if(nd.type == ClassNode.CLASS_LAMBDA) { + String parent_class_name = mapMethodsLambda.get(nd.enclosingMethod); + if(parent_class_name != null) { + ClassNode parent_class = clprocessor.getMapRootClasses().get(parent_class_name); + + parent_class.nested.add(nd); + nd.parent = parent_class; + } + } + } + + // FIXME: mixed hierarchy? + + return false; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java new file mode 100644 index 0000000..c3c180b --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java @@ -0,0 +1,274 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.rels; + +import java.io.IOException; + +import org.jetbrains.java.decompiler.code.InstructionSequence; +import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.code.DeadCodeHelper; +import org.jetbrains.java.decompiler.modules.decompiler.ClearStructHelper; +import org.jetbrains.java.decompiler.modules.decompiler.DomHelper; +import org.jetbrains.java.decompiler.modules.decompiler.ExitHelper; +import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.FinallyProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.IdeaNotNullHelper; +import org.jetbrains.java.decompiler.modules.decompiler.IfHelper; +import org.jetbrains.java.decompiler.modules.decompiler.InlineSingleBlockHelper; +import org.jetbrains.java.decompiler.modules.decompiler.LabelHelper; +import org.jetbrains.java.decompiler.modules.decompiler.LoopExtractHelper; +import org.jetbrains.java.decompiler.modules.decompiler.MergeHelper; +import org.jetbrains.java.decompiler.modules.decompiler.PPandMMHelper; +import org.jetbrains.java.decompiler.modules.decompiler.SecondaryFunctionsHelper; +import org.jetbrains.java.decompiler.modules.decompiler.SequenceHelper; +import org.jetbrains.java.decompiler.modules.decompiler.StackVarsProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.deobfuscator.ExceptionDeobfuscator; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructMethod; + +public class MethodProcessorThread implements Runnable { + + private StructMethod method; + private VarProcessor varproc; + private DecompilerContext parentContext; + + private RootStatement root; + + private Throwable error; + + public MethodProcessorThread(StructMethod method, VarProcessor varproc, + DecompilerContext parentContext) { + this.method = method; + this.varproc = varproc; + this.parentContext = parentContext; + } + + public void run() { + + DecompilerContext.setCurrentContext(parentContext); + + error = null; + root = null; + + try { + root = codeToJava(method, varproc); + + synchronized(this) { + this.notify(); + } + + } catch(ThreadDeath ex) { + ; + } catch(Throwable ex) { + error = ex; + } + + } + + public static RootStatement codeToJava(StructMethod mt, VarProcessor varproc) throws IOException { + + StructClass cl = mt.getClassStruct(); + + boolean isInitializer = "<clinit>".equals(mt.getName()); // for now static initializer only + + mt.expandData(); + InstructionSequence seq = mt.getInstructionSequence(); + ControlFlowGraph graph = new ControlFlowGraph(seq); + +// System.out.println(graph.toString()); + + +// if(mt.getName().endsWith("_getActiveServers")) { +// System.out.println(); +// } + + //DotExporter.toDotFile(graph, new File("c:\\Temp\\fern1.dot"), true); + + DeadCodeHelper.removeDeadBlocks(graph); + graph.inlineJsr(mt); + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern4.dot"), true); + + // TODO: move to the start, before jsr inlining + DeadCodeHelper.connectDummyExitBlock(graph); + + DeadCodeHelper.removeGotos(graph); + ExceptionDeobfuscator.removeCircularRanges(graph); + //DeadCodeHelper.removeCircularRanges(graph); + + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); + + ExceptionDeobfuscator.restorePopRanges(graph); + + if(DecompilerContext.getOption(IFernflowerPreferences.REMOVE_EMPTY_RANGES)) { + ExceptionDeobfuscator.removeEmptyRanges(graph); + } + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); + + if(DecompilerContext.getOption(IFernflowerPreferences.NO_EXCEPTIONS_RETURN)) { + // special case: single return instruction outside of a protected range + DeadCodeHelper.incorporateValueReturns(graph); + } + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern5.dot"), true); + +// ExceptionDeobfuscator.restorePopRanges(graph); + ExceptionDeobfuscator.insertEmptyExceptionHandlerBlocks(graph); + + DeadCodeHelper.mergeBasicBlocks(graph); + + DecompilerContext.getCountercontainer().setCounter(CounterContainer.VAR_COUNTER, mt.getLocalVariables()); + + //DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); + //System.out.println(graph.toString()); + + if(ExceptionDeobfuscator.hasObfuscatedExceptions(graph)) { + DecompilerContext.getLogger().writeMessage("Heavily obfuscated exception ranges found!", IFernflowerLogger.WARNING); + } + + RootStatement root = DomHelper.parseGraph(graph); + + if(!DecompilerContext.getOption(IFernflowerPreferences.FINALLY_CATCHALL)) { + FinallyProcessor fproc = new FinallyProcessor(varproc); + while(fproc.iterateGraph(mt, root, graph)) { + + //DotExporter.toDotFile(graph, new File("c:\\Temp\\fern2.dot"), true); + //System.out.println(graph.toString()); + + //System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + root = DomHelper.parseGraph(graph); + } + } + + // remove synchronized exception handler + // not until now because of comparison between synchronized statements in the finally cycle + DomHelper.removeSynchronizedHandler(root); + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); +// System.out.println(graph.toString()); + +// LabelHelper.lowContinueLabels(root, new HashSet<StatEdge>()); + + SequenceHelper.condenseSequences(root); + + ClearStructHelper.clearStatements(root); + + ExprProcessor proc = new ExprProcessor(); + proc.processStatement(root, cl); + +// DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); +// System.out.println(graph.toString()); + + //System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + for(;;) { + StackVarsProcessor stackproc = new StackVarsProcessor(); + stackproc.simplifyStackVars(root, mt, cl); + + //System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + varproc.setVarVersions(root); + +// System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + if(!new PPandMMHelper().findPPandMM(root)) { + break; + } + } + + for(;;) { + + LabelHelper.cleanUpEdges(root); + + for(;;) { + + MergeHelper.enhanceLoops(root); + + if(LoopExtractHelper.extractLoops(root)) { + continue; + } + + if(!IfHelper.mergeAllIfs(root)) { + break; + } + } + + if(DecompilerContext.getOption(IFernflowerPreferences.IDEA_NOT_NULL_ANNOTATION)) { + + if(IdeaNotNullHelper.removeHardcodedChecks(root, mt)) { + + SequenceHelper.condenseSequences(root); + + StackVarsProcessor stackproc = new StackVarsProcessor(); + stackproc.simplifyStackVars(root, mt, cl); + + varproc.setVarVersions(root); + } + } + + LabelHelper.identifyLabels(root); + +// System.out.println("~~~~~~~~~~~~~~~~~~~~~~ \r\n"+root.toJava()); + + if(InlineSingleBlockHelper.inlineSingleBlocks(root)) { + continue; + } + + // initializer may have at most one return point, so no transformation of method exits permitted + if(isInitializer || !ExitHelper.condenseExits(root)) { + break; + } + + // FIXME: !! +// if(!EliminateLoopsHelper.eliminateLoops(root)) { +// break; +// } + } + + ExitHelper.removeRedundantReturns(root); + + SecondaryFunctionsHelper.identifySecondaryFunctions(root); + + varproc.setVarDefinitions(root); + + // must be the last invocation, because it makes the statement structure inconsistent + // FIXME: new edge type needed + LabelHelper.replaceContinueWithBreak(root); + + mt.releaseResources(); + +// System.out.println("++++++++++++++++++++++/// \r\n"+root.toJava()); + + return root; + } + + public RootStatement getRoot() { + return root; + } + + public Throwable getError() { + return error; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java b/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java new file mode 100644 index 0000000..4f8d6f8 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java @@ -0,0 +1,62 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.rels; + +import java.util.HashSet; +import java.util.List; + +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.FlattenStatementsHelper; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructMethod; + + +public class MethodWrapper { + + public RootStatement root; + + public VarProcessor varproc; + + public StructMethod methodStruct; + + public CounterContainer counter; + + public DirectGraph graph; + + public List<VarVersionPaar> signatureFields; + + public boolean decompiledWithErrors; + + public HashSet<String> setOuterVarNames = new HashSet<String>(); + + public MethodWrapper(RootStatement root, VarProcessor varproc, StructMethod methodStruct, CounterContainer counter) { + this.root = root; + this.varproc = varproc; + this.methodStruct = methodStruct; + this.counter = counter; + } + + public DirectGraph getOrBuildGraph() { + if(graph == null && root != null) { + FlattenStatementsHelper flatthelper = new FlattenStatementsHelper(); + graph = flatthelper.buildDirectGraph(root); + } + return graph; + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java new file mode 100644 index 0000000..1c962ec --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java @@ -0,0 +1,1020 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.rels; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.Map.Entry; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +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.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.NewExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +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.DoStatement; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +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; +import org.jetbrains.java.decompiler.struct.StructClass; +import org.jetbrains.java.decompiler.struct.StructField; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.StructEnclosingMethodAttribute; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class NestedClassProcessor { + + + public void processClass(ClassNode root, ClassNode node) { + + // hide synthetic lambda content methods + if(node.type == ClassNode.CLASS_LAMBDA && !node.lambda_information.is_method_reference) { + ClassNode node_content = DecompilerContext.getClassprocessor().getMapRootClasses().get(node.classStruct.qualifiedName); + if(node_content != null && node_content.wrapper != null) { + node_content.wrapper.getHideMembers().add(node.lambda_information.content_method_key); + } + } + + if(node.nested.isEmpty()) { + return; + } + + if(node.type != ClassNode.CLASS_LAMBDA) { + + computeLocalVarsAndDefinitions(node); + + // for each local or anonymous class ensure not empty enclosing method + checkNotFoundClasses(root, node); + } + + int nameless = 0, synthetics = 0; + for(ClassNode child : node.nested) { + // ensure not-empty class name + if ((child.type == ClassNode.CLASS_LOCAL || child.type == ClassNode.CLASS_MEMBER) && child.simpleName == null) { + StructClass cl = child.classStruct; + if (((child.access | cl.access_flags) & CodeConstants.ACC_SYNTHETIC) != 0 || cl.getAttributes().containsKey("Synthetic")) { + child.simpleName = "SyntheticClass_" + (++synthetics); + } else { + DecompilerContext.getLogger().writeMessage("Nameless local or member class " + cl.qualifiedName + "!", IFernflowerLogger.WARNING); + child.simpleName = "NamelessClass_" + (++nameless); + } + } + } + + for(ClassNode child : node.nested) { + if(child.type == ClassNode.CLASS_LAMBDA) { + setLambdaVars(node, child); + } else { + if(child.type != ClassNode.CLASS_MEMBER || (child.access & CodeConstants.ACC_STATIC) == 0) { + insertLocalVars(node, child); + + if(child.type == ClassNode.CLASS_LOCAL) { + setLocalClassDefinition(node.wrapper.getMethods().getWithKey(child.enclosingMethod), child); + } + } + } + } + + for(ClassNode child : node.nested) { + processClass(root, child); + } + + } + + private void setLambdaVars(ClassNode parent, ClassNode child) { + + if(child.lambda_information.is_method_reference) { // method reference, no code and no parameters + return; + } + + final MethodWrapper meth = parent.wrapper.getMethods().getWithKey(child.lambda_information.content_method_key); + final MethodWrapper encmeth = parent.wrapper.getMethods().getWithKey(child.enclosingMethod); + + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(child.lambda_information.method_descriptor); + final MethodDescriptor md_content = MethodDescriptor.parseDescriptor(child.lambda_information.content_method_descriptor); + + final int vars_count = md_content.params.length - md_lambda.params.length; +// if(vars_count < 0) { // should not happen, but just in case... +// vars_count = 0; +// } + + final boolean is_static_lambda_content = child.lambda_information.is_content_method_static; + + final String parent_class_name = parent.wrapper.getClassStruct().qualifiedName; + final String lambda_class_name = child.simpleName; + + final VarType lambda_class_type = new VarType(lambda_class_name, true); + + // this pointer + if(!is_static_lambda_content && DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS)) { + meth.varproc.getThisvars().put(new VarVersionPaar(0, 0), parent_class_name); + meth.varproc.setVarName(new VarVersionPaar(0, 0), parent.simpleName + ".this"); + } + + // local variables + DirectGraph graph = encmeth.getOrBuildGraph(); + + final HashMap<VarVersionPaar, String> mapNewNames = new HashMap<VarVersionPaar, String>(); + + graph.iterateExprents(new DirectGraph.ExprentIterator() { + public int processExprent(Exprent exprent) { + + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + for(Exprent expr: lst) { + + if(expr.type == Exprent.EXPRENT_NEW) { + NewExprent new_expr = (NewExprent)expr; + if(new_expr.isLambda() && lambda_class_type.equals(new_expr.getNewtype())) { + + InvocationExprent inv_dynamic = new_expr.getConstructor(); + + 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); + + //meth.varproc.setVarName(new VarVersionPaar(varindex, 0), enc_varname); + mapNewNames.put(new VarVersionPaar(varindex, 0), enc_varname); + } + + varindex+=md_content.params[i].stack_size; + } + + } + } + } + + return 0; + } + }); + + // update names of local variables + HashSet<String> setNewOuterNames = new HashSet<String>(mapNewNames.values()); + setNewOuterNames.removeAll(meth.setOuterVarNames); + + meth.varproc.refreshVarNames(new VarNamesCollector(setNewOuterNames)); + meth.setOuterVarNames.addAll(setNewOuterNames); + + for(Entry<VarVersionPaar, String> entr : mapNewNames.entrySet()) { + meth.varproc.setVarName(entr.getKey(), entr.getValue()); + } + + } + + private void checkNotFoundClasses(ClassNode root, ClassNode node) { + + List<ClassNode> lstChildren = new ArrayList<ClassNode>(node.nested); + + for(ClassNode child : lstChildren) { + + if((child.type == ClassNode.CLASS_LOCAL || child.type == ClassNode.CLASS_ANONYMOUS) && child.enclosingMethod == null) { + + Set<String> setEnclosing = child.enclosingClasses; + + if(setEnclosing.size() == 1) { + StructEnclosingMethodAttribute attr = (StructEnclosingMethodAttribute)child.classStruct.getAttributes().getWithKey("EnclosingMethod"); + if(attr != null && attr.getMethodName() != null) { + if(node.classStruct.qualifiedName.equals(attr.getClassname()) && + node.classStruct.getMethod(attr.getMethodName(), attr.getMethodDescriptor()) != null) { + child.enclosingMethod = InterpreterUtil.makeUniqueKey(attr.getMethodName(), attr.getMethodDescriptor()); + continue; + } + } + } + + node.nested.remove(child); + child.parent = null; + setEnclosing.remove(node.classStruct.qualifiedName); + + boolean hasEnclosing = !setEnclosing.isEmpty(); + if(hasEnclosing) { + hasEnclosing = insertNestedClass(root, child); + } + + if(!hasEnclosing) { + if(child.type == ClassNode.CLASS_ANONYMOUS) { + DecompilerContext.getLogger().writeMessage("Unreferenced anonymous class "+child.classStruct.qualifiedName+"!", IFernflowerLogger.WARNING); + } else if(child.type == ClassNode.CLASS_LOCAL) { + DecompilerContext.getLogger().writeMessage("Unreferenced local class "+child.classStruct.qualifiedName+"!", IFernflowerLogger.WARNING); + } + } + } + } + } + + private boolean insertNestedClass(ClassNode root, ClassNode child) { + + Set<String> setEnclosing = child.enclosingClasses; + + LinkedList<ClassNode> stack = new LinkedList<ClassNode>(); + stack.add(root); + + while(!stack.isEmpty()) { + + ClassNode node = stack.removeFirst(); + + if(setEnclosing.contains(node.classStruct.qualifiedName)) { + node.nested.add(child); + child.parent = node; + + return true; + } + + // note: ordered list + stack.addAll(node.nested); + } + + return false; + } + + + private void computeLocalVarsAndDefinitions(final ClassNode node) { + + // local var masks + // class name, constructor descriptor, field mask + final HashMap<String, HashMap<String, List<VarFieldPair>>> mapVarMasks = new HashMap<String, HashMap<String, List<VarFieldPair>>>(); + + int cltypes = 0; + + for(ClassNode nd: node.nested) { + if(nd.type != ClassNode.CLASS_LAMBDA) { + if((nd.access & CodeConstants.ACC_STATIC) == 0 && (nd.access & CodeConstants.ACC_INTERFACE) == 0) { + + cltypes |= nd.type; + + HashMap<String, List<VarFieldPair>> mask = getMaskLocalVars(nd.wrapper); + if(mask.isEmpty()) { + DecompilerContext.getLogger().writeMessage("Nested class "+nd.classStruct.qualifiedName+" has no constructor!", IFernflowerLogger.WARNING); + } else { + mapVarMasks.put(nd.classStruct.qualifiedName, mask); + } + } + } + } + + // local var masks + final HashMap<String, HashMap<String, List<VarFieldPair>>> mapVarFieldPairs = new HashMap<String, HashMap<String, List<VarFieldPair>>>(); + + if(cltypes != ClassNode.CLASS_MEMBER) { + + // iterate enclosing class + for(final MethodWrapper meth: node.wrapper.getMethods()) { + + if(meth.root != null) { // neither abstract, nor native + DirectGraph graph = meth.getOrBuildGraph(); + + graph.iterateExprents(new DirectGraph.ExprentIterator() { + public int processExprent(Exprent exprent) { + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + for(Exprent expr: lst) { + + if(expr.type == Exprent.EXPRENT_NEW) { + InvocationExprent constr = ((NewExprent)expr).getConstructor(); + + if(constr != null && mapVarMasks.containsKey(constr.getClassname())) { // non-static inner class constructor + + String refclname = constr.getClassname(); + + ClassNode nestedClassNode = node.getClassNode(refclname); + + if(nestedClassNode.type != ClassNode.CLASS_MEMBER) { + + List<VarFieldPair> mask = mapVarMasks.get(refclname).get(constr.getStringDescriptor()); + + if(!mapVarFieldPairs.containsKey(refclname)) { + mapVarFieldPairs.put(refclname, new HashMap<String, List<VarFieldPair>>()); + } + + List<VarFieldPair> lstTemp = new ArrayList<VarFieldPair>(); + + for(int i=0;i<mask.size();i++) { + Exprent param = constr.getLstParameters().get(i); + VarFieldPair pair = null; + + if(param.type == Exprent.EXPRENT_VAR && mask.get(i) != null) { + VarVersionPaar varpaar = new VarVersionPaar((VarExprent)param); + + // FIXME: final flags of variables are wrong! Correct the entire final functionality. +// if(meth.varproc.getVarFinal(varpaar) != VarTypeProcessor.VAR_NONFINAL) { + pair = new VarFieldPair(mask.get(i).keyfield, varpaar); +// } + } + + lstTemp.add(pair); + } + + List<VarFieldPair> pairmask = mapVarFieldPairs.get(refclname).get(constr.getStringDescriptor()); + + if(pairmask == null) { + pairmask = lstTemp; + } else { + for(int i=0;i<pairmask.size();i++) { + if(!InterpreterUtil.equalObjects(pairmask.get(i), lstTemp.get(i))) { + pairmask.set(i, null); + } + } + } + + mapVarFieldPairs.get(refclname).put(constr.getStringDescriptor(), pairmask); + nestedClassNode.enclosingMethod = InterpreterUtil.makeUniqueKey(meth.methodStruct.getName(), meth.methodStruct.getDescriptor()); + } + } + } + + } + return 0; + } + }); + } + } + } + + // merge var masks + for(Entry<String, HashMap<String, List<VarFieldPair>>> entcl : mapVarMasks.entrySet()) { + + ClassNode nestedNode = node.getClassNode(entcl.getKey()); + + // intersection + List<VarFieldPair> intrPairMask = null; + // merge referenced constructors + if(mapVarFieldPairs.containsKey(entcl.getKey())) { + for(List<VarFieldPair> mask : mapVarFieldPairs.get(entcl.getKey()).values()) { + if(intrPairMask == null) { + intrPairMask = new ArrayList<VarFieldPair>(mask); + } else { + mergeListSignatures(intrPairMask, mask, false); + } + } + } + + List<VarFieldPair> intrMask = null; + // merge all constructors + for(List<VarFieldPair> mask : entcl.getValue().values()) { + if(intrMask == null) { + intrMask = new ArrayList<VarFieldPair>(mask); + } else { + mergeListSignatures(intrMask, mask, false); + } + } + + if(intrPairMask == null) { // member or local and never instantiated + intrPairMask = new ArrayList<VarFieldPair>(intrMask); + + boolean found = false; + + for(int i=0;i<intrPairMask.size();i++) { + if(intrPairMask.get(i) != null) { + if(found) { + intrPairMask.set(i, null); + } + found = true; + } + } + } + + mergeListSignatures(intrPairMask, intrMask, true); + + for(int i=0;i<intrPairMask.size();i++) { + VarFieldPair pair = intrPairMask.get(i); + if(pair != null && pair.keyfield.length() > 0) { + nestedNode.mapFieldsToVars.put(pair.keyfield, pair.varpaar); + } + } + + // set resulting constructor signatures + for(Entry<String, List<VarFieldPair>> entmt : entcl.getValue().entrySet()) { + mergeListSignatures(entmt.getValue(), intrPairMask, false); + + MethodWrapper meth = nestedNode.wrapper.getMethodWrapper("<init>", entmt.getKey()); + meth.signatureFields = new ArrayList<VarVersionPaar>(); + + for(VarFieldPair pair : entmt.getValue()) { + meth.signatureFields.add(pair==null?null:pair.varpaar); + } + } + + } + + } + + private void insertLocalVars(final ClassNode parent, final ClassNode child) { + + // enclosing method, is null iff member class + MethodWrapper encmeth = parent.wrapper.getMethods().getWithKey(child.enclosingMethod); + + // iterate all child methods + for(final MethodWrapper meth : child.wrapper.getMethods()) { + + if(meth.root != null) { // neither abstract nor native + + // local var names + HashMap<VarVersionPaar, String> mapNewNames = new HashMap<VarVersionPaar, String>(); + // local var types + HashMap<VarVersionPaar, VarType> mapNewTypes = new HashMap<VarVersionPaar, VarType>(); + + final HashMap<Integer, VarVersionPaar> mapParamsToNewVars = new HashMap<Integer, VarVersionPaar>(); + if(meth.signatureFields != null) { + int index = 0; + int varindex = 1; + MethodDescriptor md = MethodDescriptor.parseDescriptor(meth.methodStruct.getDescriptor()); + + for(VarVersionPaar paar : meth.signatureFields) { + if(paar != null) { + VarVersionPaar newvar = new VarVersionPaar(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0); + + mapParamsToNewVars.put(varindex, newvar); + + String varname = null; + VarType vartype = null; + + if(child.type != ClassNode.CLASS_MEMBER) { + varname = encmeth.varproc.getVarName(paar); + vartype = encmeth.varproc.getVarType(paar); + + encmeth.varproc.setVarFinal(paar, VarTypeProcessor.VAR_FINALEXPLICIT); + } + + if(paar.var == -1 || "this".equals(varname)) { + if(parent.simpleName == null) { + // anonymous enclosing class, no access to this + varname = VarExprent.VAR_NAMELESS_ENCLOSURE; + } else { + varname = parent.simpleName+".this"; + } + meth.varproc.getThisvars().put(newvar, parent.classStruct.qualifiedName); + } + + mapNewNames.put(newvar, varname); + mapNewTypes.put(newvar, vartype); + } + varindex+=md.params[index++].stack_size; + } + } + + // new vars + final HashMap<String, VarVersionPaar> mapFieldsToNewVars = new HashMap<String, VarVersionPaar>(); + + for(ClassNode clnode = child; clnode != null; clnode = clnode.parent) { + + for(Entry<String, VarVersionPaar> entr : clnode.mapFieldsToVars.entrySet()) { + VarVersionPaar newvar = new VarVersionPaar(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0); + + mapFieldsToNewVars.put(InterpreterUtil.makeUniqueKey(clnode.classStruct.qualifiedName, entr.getKey()), newvar); + + String varname = null; + VarType vartype = null; + + if(clnode.type != ClassNode.CLASS_MEMBER) { + + MethodWrapper enclosing_method = clnode.parent.wrapper.getMethods().getWithKey(clnode.enclosingMethod); + + varname = enclosing_method.varproc.getVarName(entr.getValue()); + vartype = enclosing_method.varproc.getVarType(entr.getValue()); + + enclosing_method.varproc.setVarFinal(entr.getValue(), VarTypeProcessor.VAR_FINALEXPLICIT); + } + + if(entr.getValue().var == -1 || "this".equals(varname)) { + if(clnode.parent.simpleName == null) { + // anonymous enclosing class, no access to this + varname = VarExprent.VAR_NAMELESS_ENCLOSURE; + } else { + varname = clnode.parent.simpleName+".this"; + } + meth.varproc.getThisvars().put(newvar, clnode.parent.classStruct.qualifiedName); + } + + mapNewNames.put(newvar, varname); + mapNewTypes.put(newvar, vartype); + + // hide synthetic field + if(clnode == child) { // fields higher up the chain were already handled with their classes + StructField fd = child.classStruct.getFields().getWithKey(entr.getKey()); + child.wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + } + } + } + + HashSet<String> setNewOuterNames = new HashSet<String>(mapNewNames.values()); + setNewOuterNames.removeAll(meth.setOuterVarNames); + + meth.varproc.refreshVarNames(new VarNamesCollector(setNewOuterNames)); + meth.setOuterVarNames.addAll(setNewOuterNames); + + for(Entry<VarVersionPaar, String> entr : mapNewNames.entrySet()) { + VarVersionPaar varpaar = entr.getKey(); + VarType vartype = mapNewTypes.get(varpaar); + + meth.varproc.setVarName(varpaar, entr.getValue()); + if(vartype != null) { + meth.varproc.setVarType(varpaar, vartype); + } + } + + DirectGraph graph = meth.getOrBuildGraph(); + + graph.iterateExprents(new DirectGraph.ExprentIterator() { + public int processExprent(Exprent exprent) { + + if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent asexpr = (AssignmentExprent)exprent; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) { + FieldExprent fexpr = (FieldExprent)asexpr.getLeft(); + + if(fexpr.getClassname().equals(child.classStruct.qualifiedName) && // process this class only + mapFieldsToNewVars.containsKey(InterpreterUtil.makeUniqueKey(child.classStruct.qualifiedName, + InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString)))) { + return 2; + } + + //if(fexpr.getClassname().equals(child.classStruct.qualifiedName) && + // mapFieldsToNewVars.containsKey(InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString))) { + // return 2; + //} + } + } + + if(child.type == ClassNode.CLASS_ANONYMOUS && "<init>".equals(meth.methodStruct.getName()) + && exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent)exprent; + if(invexpr.getFunctype() == InvocationExprent.TYP_INIT) { + // invocation of the super constructor in an anonymous class + child.superInvocation = invexpr; // FIXME: save original names of parameters + return 2; + } + } + + replaceExprent(exprent); + + return 0; + } + + private Exprent replaceExprent(Exprent exprent) { + + if(exprent.type == Exprent.EXPRENT_VAR) { + int varindex = ((VarExprent)exprent).getIndex(); + if(mapParamsToNewVars.containsKey(varindex)) { + VarVersionPaar newvar = mapParamsToNewVars.get(varindex); + meth.varproc.getExternvars().add(newvar); + return new VarExprent(newvar.var, meth.varproc.getVarType(newvar), meth.varproc); + } + } else if(exprent.type == Exprent.EXPRENT_FIELD) { + FieldExprent fexpr = (FieldExprent)exprent; + + String keyField = InterpreterUtil.makeUniqueKey(fexpr.getClassname(), InterpreterUtil.makeUniqueKey(fexpr.getName(), fexpr.getDescriptor().descriptorString)); + + if(mapFieldsToNewVars.containsKey(keyField)) { + //if(fexpr.getClassname().equals(child.classStruct.qualifiedName) && + // mapFieldsToNewVars.containsKey(keyField)) { + VarVersionPaar newvar = mapFieldsToNewVars.get(keyField); + meth.varproc.getExternvars().add(newvar); + return new VarExprent(newvar.var, meth.varproc.getVarType(newvar), meth.varproc); + } + } + + boolean replaced = true; + while(replaced) { + replaced = false; + + for(Exprent expr: exprent.getAllExprents()) { + Exprent retexpr = replaceExprent(expr); + if(retexpr != null) { + exprent.replaceExprent(expr, retexpr); + replaced = true; + break; + } + } + } + + return null; + } + }); + + } + } + + } + + private HashMap<String, List<VarFieldPair>> getMaskLocalVars(ClassWrapper wrapper) { + + HashMap<String, List<VarFieldPair>> mapMasks = new HashMap<String, List<VarFieldPair>>(); + + StructClass cl = wrapper.getClassStruct(); + + // iterate over constructors + for(StructMethod mt: cl.getMethods()) { + if("<init>".equals(mt.getName())) { + + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); + + MethodWrapper meth = wrapper.getMethodWrapper("<init>", mt.getDescriptor()); + DirectGraph graph = meth.getOrBuildGraph(); + + if(graph != null) { // something gone wrong, should not be null + List<VarFieldPair> fields = new ArrayList<VarFieldPair>(); + + int varindex = 1; + for(int i=0;i<md.params.length;i++) { // no static methods allowed + String keyField = getEnclosingVarField(cl, meth, graph, varindex); + fields.add(keyField==null?null:new VarFieldPair(keyField, new VarVersionPaar(-1, 0))); // TODO: null? + varindex+=md.params[i].stack_size; + } + mapMasks.put(mt.getDescriptor(), fields); + } + } + } + + return mapMasks; + } + + private String getEnclosingVarField(StructClass cl, MethodWrapper meth, DirectGraph graph, final int index) { + + String field = ""; + + // parameter variable final + if(meth.varproc.getVarFinal(new VarVersionPaar(index, 0)) == VarTypeProcessor.VAR_NONFINAL) { + return null; + } + + boolean notsynth = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + // no loop at the begin + DirectNode firstnode = graph.first; + if(firstnode.preds.isEmpty()) { + // assignment to a final synthetic field? + for(Exprent exprent: firstnode.exprents) { + if(exprent.type == Exprent.EXPRENT_ASSIGNMENT) { + AssignmentExprent asexpr = (AssignmentExprent)exprent; + if(asexpr.getRight().type == Exprent.EXPRENT_VAR && ((VarExprent)asexpr.getRight()).getIndex() == index) { + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD) { + + FieldExprent left = (FieldExprent)asexpr.getLeft(); + StructField fd = cl.getField(left.getName(), left.getDescriptor().descriptorString); + + if(fd != null) { // local (== not inherited) field + if(cl.qualifiedName.equals(left.getClassname()) && + (fd.access_flags & CodeConstants.ACC_FINAL) != 0 && + ((fd.access_flags & CodeConstants.ACC_SYNTHETIC) != 0 || + fd.getAttributes().containsKey("Synthetic") || + (notsynth && (fd.access_flags & CodeConstants.ACC_PRIVATE) != 0))) { + field = InterpreterUtil.makeUniqueKey(left.getName(), left.getDescriptor().descriptorString); + break; + } + } + } + } + } + } + } + + return field; + } + + private void mergeListSignatures(List<VarFieldPair> first, List<VarFieldPair> second, boolean both) { + + int i=1; + for(;;) { + if(first.size() <= i || second.size() <= i) { + break; + } + + VarFieldPair fobj = first.get(first.size() - i); + VarFieldPair sobj = second.get(second.size() - i); + + boolean eq = false; + if(fobj == null || sobj == null) { + eq = (fobj == sobj); + } else { + eq = true; + if(fobj.keyfield.length()==0) { + fobj.keyfield = sobj.keyfield; + } else if(sobj.keyfield.length() == 0) { + if(both) { + sobj.keyfield = fobj.keyfield; + } + } else { + eq = fobj.keyfield.equals(sobj.keyfield); + } + } + + if(!eq) { + first.set(first.size() - i, null); + if(both) { + second.set(second.size() - i, null); + } + } else { + if(fobj != null) { + if(fobj.varpaar.var == -1) { + fobj.varpaar = sobj.varpaar; + } else { + sobj.varpaar = fobj.varpaar; + } + } + } + i++; + } + + for(int j=1;j<=first.size()-i;j++) { + first.set(j, null); + } + + if(both) { + for(int j=1;j<=second.size()-i;j++) { + second.set(j, null); + } + } + + // first + if(first.isEmpty()) { + if(!second.isEmpty() && both) { + second.set(0, null); + } + } else if(second.isEmpty()) { + first.set(0, null); + } else { + VarFieldPair fobj = first.get(0); + VarFieldPair sobj = second.get(0); + + boolean eq = false; + if(fobj == null || sobj == null) { + eq = (fobj == sobj); + } else { + eq = true; + if(fobj.keyfield.length()==0) { + fobj.keyfield = sobj.keyfield; + } else if(sobj.keyfield.length() == 0) { + if(both) { + sobj.keyfield = fobj.keyfield; + } + } else { + eq = fobj.keyfield.equals(sobj.keyfield); + } + } + + if(!eq) { + first.set(0, null); + if(both) { + second.set(0, null); + } + } else if(fobj != null) { + if(fobj.varpaar.var == -1) { + fobj.varpaar = sobj.varpaar; + } else { + sobj.varpaar = fobj.varpaar; + } + } + } + + } + + + private void setLocalClassDefinition(MethodWrapper meth, ClassNode node) { + + RootStatement root = meth.root; + + HashSet<Statement> setStats = new HashSet<Statement>(); + VarType classtype = new VarType(node.classStruct.qualifiedName, true); + + Statement stdef = getDefStatement(root, classtype, setStats); + if(stdef == null) { + // unreferenced local class + stdef = root.getFirst(); + } + + Statement first = findFirstBlock(stdef, setStats); + + List<Exprent> lst; + if(first == null) { + lst = stdef.getVarDefinitions(); + } else if(first.getExprents() == null) { + lst = first.getVarDefinitions(); + } else { + lst = first.getExprents(); + } + + + int addindex = 0; + for(Exprent expr: lst) { + if(searchForClass(expr, classtype)) { + break; + } + addindex++; + } + + VarExprent var = new VarExprent(meth.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), + classtype, meth.varproc); + var.setDefinition(true); + var.setClassdef(true); + + lst.add(addindex, var); + + } + + + + private Statement findFirstBlock(Statement stat, HashSet<Statement> setStats) { + + LinkedList<Statement> stack = new LinkedList<Statement>(); + stack.add(stat); + + while(!stack.isEmpty()) { + Statement st = stack.remove(0); + + if(stack.isEmpty() || setStats.contains(st)) { + + if(st.isLabeled() && !stack.isEmpty()) { + return st; + } + + if(st.getExprents() != null) { + return st; + } else { + stack.clear(); + + switch(st.type) { + case Statement.TYPE_SEQUENCE: + stack.addAll(0, st.getStats()); + break; + case Statement.TYPE_IF: + case Statement.TYPE_ROOT: + case Statement.TYPE_SWITCH: + case Statement.TYPE_SYNCRONIZED: + stack.add(st.getFirst()); + break; + default: + return st; + } + } + } + } + + return null; + } + + + private Statement getDefStatement(Statement stat, VarType classtype, HashSet<Statement> setStats) { + + List<Exprent> condlst = new ArrayList<Exprent>(); + Statement retstat = null; + + if(stat.getExprents() == null) { + int counter = 0; + + for(Object obj: stat.getSequentialObjects()) { + if(obj instanceof Statement) { + Statement st = (Statement)obj; + + Statement stTemp = getDefStatement(st, classtype, setStats); + + if(stTemp != null) { + if(counter == 1) { + retstat = stat; + break; + } + retstat = stTemp; + counter++; + } + + if(st.type == DoStatement.TYPE_DO) { + DoStatement dost = (DoStatement)st; + + condlst.addAll(dost.getInitExprentList()); + condlst.addAll(dost.getConditionExprentList()); + } + + } else if(obj instanceof Exprent) { + condlst.add((Exprent)obj); + } + } + } else { + condlst = stat.getExprents(); + } + + if(retstat != stat) { + for(Exprent exprent : condlst) { + if(exprent!=null && searchForClass(exprent, classtype)) { + retstat = stat; + break; + } + } + } + + if(retstat != null) { + setStats.add(stat); + } + + return retstat; + } + + private boolean searchForClass(Exprent exprent, VarType classtype) { + + List<Exprent> lst = exprent.getAllExprents(true); + lst.add(exprent); + + String classname = classtype.value; + + for(Exprent expr : lst) { + + boolean res = false; + + switch(expr.type) { + case Exprent.EXPRENT_CONST: + ConstExprent cexpr = (ConstExprent)expr; + res = (VarType.VARTYPE_CLASS.equals(cexpr.getConsttype()) && classname.equals(cexpr.getValue()) || + classtype.equals(cexpr.getConsttype())); + break; + case Exprent.EXPRENT_FIELD: + res = classname.equals(((FieldExprent)expr).getClassname()); + break; + case Exprent.EXPRENT_INVOCATION: + res = classname.equals(((InvocationExprent)expr).getClassname()); + break; + case Exprent.EXPRENT_NEW: + VarType newType = expr.getExprType(); + res = newType.type == CodeConstants.TYPE_OBJECT && classname.equals(newType.value); + break; + case Exprent.EXPRENT_VAR: + VarExprent vexpr = (VarExprent)expr; + if(vexpr.isDefinition()) { + VarType vtype = vexpr.getVartype(); + if(classtype.equals(vtype) || (vtype.arraydim > 0 && classtype.value.equals(vtype.value))) { + res = true; + } + } + } + + if(res) { + return true; + } + } + + return false; + } + + + private class VarFieldPair { + + public String keyfield = ""; + public VarVersionPaar varpaar; + + public VarFieldPair(String field, VarVersionPaar varpaar) { + this.keyfield = field; + this.varpaar = varpaar; + } + + @Override + public boolean equals(Object o) { + if(o == this) return true; + if(o == null || !(o instanceof VarFieldPair)) return false; + + VarFieldPair pair = (VarFieldPair)o; + return keyfield.equals(pair.keyfield) && varpaar.equals(pair.varpaar); + } + + @Override + public int hashCode() { + return keyfield.hashCode()+varpaar.hashCode(); + } + + } + +} diff --git a/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java b/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java new file mode 100644 index 0000000..3eda632 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java @@ -0,0 +1,457 @@ +/* + * Fernflower - The Analytical Java Decompiler + * http://www.reversed-java.com + * + * (C) 2008 - 2010, Stiver + * + * This software is NEITHER public domain NOR free software + * as per GNU License. See license.txt for more details. + * + * This software is distributed WITHOUT ANY WARRANTY; without + * even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. + */ + +package org.jetbrains.java.decompiler.main.rels; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.DecompilerContext; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +import org.jetbrains.java.decompiler.main.collectors.CounterContainer; +import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; +import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.exps.AssignmentExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.ExitExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.FieldExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.InvocationExprent; +import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; +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.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +public class NestedMemberAccess { + + private static final int METHOD_ACCESS_NORMAL = 1; + private static final int METHOD_ACCESS_FIELDGET = 2; + private static final int METHOD_ACCESS_FIELDSET = 3; + private static final int METHOD_ACCESS_METHOD = 4; + + private boolean notSetSync; + + private HashMap<MethodWrapper, Integer> mapMethodType = new HashMap<MethodWrapper, Integer>(); + + + public void propagateMemberAccess(ClassNode root) { + + if(root.nested.isEmpty()) { + return; + } + + notSetSync = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + computeMethodTypes(root); + + eliminateStaticAccess(root); + } + + + private void computeMethodTypes(ClassNode node) { + + if(node.type == ClassNode.CLASS_LAMBDA) { + return; + } + + for(ClassNode nd : node.nested) { + computeMethodTypes(nd); + } + + for(MethodWrapper meth : node.wrapper.getMethods()) { + computeMethodType(node, meth); + } + + } + + private void computeMethodType(ClassNode node, MethodWrapper meth) { + + int type = METHOD_ACCESS_NORMAL; + + if(meth.root != null) { + + DirectGraph graph = meth.getOrBuildGraph(); + + int flags = meth.methodStruct.getAccessFlags(); + if(((flags & CodeConstants.ACC_SYNTHETIC) != 0 || meth.methodStruct.getAttributes().containsKey("Synthetic") || notSetSync) && + (flags & CodeConstants.ACC_STATIC) != 0) { + if(graph.nodes.size() == 2) { // incl. dummy exit node + if(graph.first.exprents.size() == 1) { + Exprent exprent = graph.first.exprents.get(0); + + MethodDescriptor mtdesc = MethodDescriptor.parseDescriptor(meth.methodStruct.getDescriptor()); + int parcount = mtdesc.params.length; + + Exprent exprCore = exprent; + + if(exprent.type == Exprent.EXPRENT_EXIT) { + ExitExprent exexpr = (ExitExprent)exprent; + if(exexpr.getExittype() == ExitExprent.EXIT_RETURN && exexpr.getValue() != null) { + exprCore = exexpr.getValue(); + } + } + + switch(exprCore.type) { + case Exprent.EXPRENT_FIELD: + FieldExprent fexpr = (FieldExprent)exprCore; + if((parcount == 1 && !fexpr.isStatic()) || + (parcount == 0 && fexpr.isStatic())) { + if(fexpr.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field + if(fexpr.isStatic() || (fexpr.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpr.getInstance()).getIndex() == 0)) { + type = METHOD_ACCESS_FIELDGET; + } + } + } + break; + case Exprent.EXPRENT_VAR: // qualified this + if(parcount == 1) { + // this or final variable + if(((VarExprent)exprCore).getIndex() != 0) { + type = METHOD_ACCESS_FIELDGET; + } + } + + break; + case Exprent.EXPRENT_INVOCATION: + type = METHOD_ACCESS_METHOD; + break; + case Exprent.EXPRENT_ASSIGNMENT: + AssignmentExprent asexpr = (AssignmentExprent)exprCore; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD && asexpr.getRight().type == Exprent.EXPRENT_VAR) { + FieldExprent fexpras = (FieldExprent)asexpr.getLeft(); + if((parcount == 2 && !fexpras.isStatic()) || + (parcount == 1 && fexpras.isStatic())) { + if(fexpras.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field + if(fexpras.isStatic() || (fexpras.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpras.getInstance()).getIndex() == 0)) { + if(((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { + type = METHOD_ACCESS_FIELDSET; + } + } + } + } + } + } + + + if(type == METHOD_ACCESS_METHOD) { // FIXME: check for private flag of the method + + type = METHOD_ACCESS_NORMAL; + + InvocationExprent invexpr = (InvocationExprent)exprCore; + + if((invexpr.isStatic() && invexpr.getLstParameters().size() == parcount) || (!invexpr.isStatic() && invexpr.getInstance().type == Exprent.EXPRENT_VAR + && ((VarExprent)invexpr.getInstance()).getIndex() == 0 && invexpr.getLstParameters().size() == parcount-1)) { + + boolean equalpars = true; + + for(int i=0;i<invexpr.getLstParameters().size();i++) { + Exprent parexpr = invexpr.getLstParameters().get(i); + if(parexpr.type != Exprent.EXPRENT_VAR || + ((VarExprent)parexpr).getIndex() != i + (invexpr.isStatic()?0:1)) { + equalpars = false; + break; + } + } + + if(equalpars) { + type = METHOD_ACCESS_METHOD; + } + } + } + } else if(graph.first.exprents.size() == 2) { + Exprent exprentFirst = graph.first.exprents.get(0); + Exprent exprentSecond = graph.first.exprents.get(1); + + if(exprentFirst.type == Exprent.EXPRENT_ASSIGNMENT && + exprentSecond.type == Exprent.EXPRENT_EXIT) { + + MethodDescriptor mtdesc = MethodDescriptor.parseDescriptor(meth.methodStruct.getDescriptor()); + int parcount = mtdesc.params.length; + + AssignmentExprent asexpr = (AssignmentExprent)exprentFirst; + if(asexpr.getLeft().type == Exprent.EXPRENT_FIELD && asexpr.getRight().type == Exprent.EXPRENT_VAR) { + FieldExprent fexpras = (FieldExprent)asexpr.getLeft(); + if((parcount == 2 && !fexpras.isStatic()) || + (parcount == 1 && fexpras.isStatic())) { + if(fexpras.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field + if(fexpras.isStatic() || (fexpras.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpras.getInstance()).getIndex() == 0)) { + if(((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { + + ExitExprent exexpr = (ExitExprent)exprentSecond; + if(exexpr.getExittype() == ExitExprent.EXIT_RETURN && exexpr.getValue() != null) { + if(exexpr.getValue().type == Exprent.EXPRENT_VAR && + ((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { + type = METHOD_ACCESS_FIELDSET; + } + } + } + } + } + } + } + } + } + + + } + } + } + + if(type != METHOD_ACCESS_NORMAL) { + mapMethodType.put(meth, type); + } else { + mapMethodType.remove(meth); + } + } + + + + private void eliminateStaticAccess(ClassNode node) { + + if(node.type == ClassNode.CLASS_LAMBDA) { + return; + } + + for(MethodWrapper meth : node.wrapper.getMethods()) { + + if(meth.root != null) { + + boolean replaced = false; + + DirectGraph graph = meth.getOrBuildGraph(); + + HashSet<DirectNode> setVisited = new HashSet<DirectNode>(); + LinkedList<DirectNode> stack = new LinkedList<DirectNode>(); + stack.add(graph.first); + + while(!stack.isEmpty()) { // TODO: replace with interface iterator? + + DirectNode nd = stack.removeFirst(); + + if(setVisited.contains(nd)) { + continue; + } + setVisited.add(nd); + + for(int i=0;i<nd.exprents.size();i++) { + Exprent exprent = nd.exprents.get(i); + + replaced |= replaceInvocations(node, meth, exprent); + + if(exprent.type == Exprent.EXPRENT_INVOCATION) { + Exprent ret = replaceAccessExprent(node, meth, (InvocationExprent)exprent); + + if(ret != null) { + nd.exprents.set(i, ret); + replaced = true; + } + } + } + + for(DirectNode ndx: nd.succs) { + stack.add(ndx); + } + } + + if(replaced) { + computeMethodType(node, meth); + } + + } + } + + for(ClassNode child : node.nested) { + eliminateStaticAccess(child); + } + + } + + + private boolean replaceInvocations(ClassNode caller, MethodWrapper meth, Exprent exprent) { + + boolean res = false; + + for(Exprent expr : exprent.getAllExprents()) { + res |= replaceInvocations(caller, meth, expr); + } + + for(;;) { + + boolean found = false; + + for(Exprent expr : exprent.getAllExprents()) { + if(expr.type == Exprent.EXPRENT_INVOCATION) { + Exprent newexpr = replaceAccessExprent(caller, meth, (InvocationExprent)expr); + if(newexpr != null) { + exprent.replaceExprent(expr, newexpr); + found = true; + res = true; + break; + } + } + } + + if(!found) { + break; + } + } + + return res; + } + + private boolean sameTree(ClassNode caller, ClassNode callee) { + + if(caller.classStruct.qualifiedName.equals(callee.classStruct.qualifiedName)) { + return false; + } + + while(caller.parent != null) { + caller = caller.parent; + } + + while(callee.parent != null) { + callee = callee.parent; + } + + return caller == callee; + } + + private Exprent replaceAccessExprent(ClassNode caller, MethodWrapper methdest, InvocationExprent invexpr) { + + ClassNode node = DecompilerContext.getClassprocessor().getMapRootClasses().get(invexpr.getClassname()); + + MethodWrapper methsource = null; + if(node != null && node.wrapper != null) { + methsource = node.wrapper.getMethodWrapper(invexpr.getName(), invexpr.getStringDescriptor()); + } + + if(methsource == null || !mapMethodType.containsKey(methsource)) { + return null; + } + + // if same method, return + if(node.classStruct.qualifiedName.equals(caller.classStruct.qualifiedName) && + methsource.methodStruct.getName().equals(methdest.methodStruct.getName()) && + methsource.methodStruct.getDescriptor().equals(methdest.methodStruct.getDescriptor())) { + // no recursive invocations permitted! + return null; + } + + int type = mapMethodType.get(methsource); + +// // FIXME: impossible case. METHOD_ACCESS_NORMAL is not saved in the map +// if(type == METHOD_ACCESS_NORMAL) { +// return null; +// } + + if(!sameTree(caller, node)) { + return null; + } + + DirectGraph graph = methsource.getOrBuildGraph(); + Exprent source = graph.first.exprents.get(0); + + Exprent retexprent = null; + + switch(type) { + case METHOD_ACCESS_FIELDGET: + ExitExprent exsource = (ExitExprent)source; + if(exsource.getValue().type == Exprent.EXPRENT_VAR) { // qualified this + VarExprent var = (VarExprent)exsource.getValue(); + String varname = methsource.varproc.getVarName(new VarVersionPaar(var)); + + if(!methdest.setOuterVarNames.contains(varname)) { + VarNamesCollector vnc = new VarNamesCollector(); + vnc.addName(varname); + + methdest.varproc.refreshVarNames(vnc); + methdest.setOuterVarNames.add(varname); + } + + int index = methdest.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER); + VarExprent ret = new VarExprent(index, var.getVartype(), methdest.varproc); + methdest.varproc.setVarName(new VarVersionPaar(index, 0), varname); + + retexprent = ret; + } else { // field + FieldExprent ret = (FieldExprent)exsource.getValue().copy(); + if(!ret.isStatic()) { + ret.replaceExprent(ret.getInstance(), invexpr.getLstParameters().get(0)); + } + retexprent = ret; + } + break; + case METHOD_ACCESS_FIELDSET: + AssignmentExprent ret; + if(source.type == Exprent.EXPRENT_EXIT) { + ExitExprent extex = (ExitExprent)source; + ret = (AssignmentExprent)((AssignmentExprent)extex.getValue()).copy(); + } else { + ret = (AssignmentExprent)((AssignmentExprent)source).copy(); + } + FieldExprent fexpr = (FieldExprent)ret.getLeft(); + + if(fexpr.isStatic()) { + ret.replaceExprent(ret.getRight(), invexpr.getLstParameters().get(0)); + } else { + ret.replaceExprent(ret.getRight(), invexpr.getLstParameters().get(1)); + fexpr.replaceExprent(fexpr.getInstance(), invexpr.getLstParameters().get(0)); + } + retexprent = ret; + break; + case METHOD_ACCESS_METHOD: + if(source.type == Exprent.EXPRENT_EXIT) { + source = ((ExitExprent)source).getValue(); + } + + InvocationExprent invret = (InvocationExprent)source.copy(); + + int index = 0; + if(!invret.isStatic()) { + invret.replaceExprent(invret.getInstance(), invexpr.getLstParameters().get(0)); + index = 1; + } + + for(int i=0;i<invret.getLstParameters().size();i++) { + invret.replaceExprent(invret.getLstParameters().get(i), invexpr.getLstParameters().get(i + index)); + } + + retexprent = invret; + } + + + if(retexprent != null) { + // hide synthetic access method + boolean hide = true; + + if(node.type == ClassNode.CLASS_ROOT || (node.access & CodeConstants.ACC_STATIC) != 0) { + StructMethod mt = methsource.methodStruct; + if((mt.getAccessFlags() & CodeConstants.ACC_SYNTHETIC) == 0 && !mt.getAttributes().containsKey("Synthetic")) { + hide = false; + } + } + if(hide) { + node.wrapper.getHideMembers().add(InterpreterUtil.makeUniqueKey(invexpr.getName(), invexpr.getStringDescriptor())); + } + } + + return retexprent; + } + + +} |