diff options
Diffstat (limited to 'plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main')
25 files changed, 5757 insertions, 0 deletions
diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/AssertProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/AssertProcessor.java new file mode 100644 index 000000000000..a58b68903563 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/AssertProcessor.java @@ -0,0 +1,310 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main; + +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.*; +import org.jetbrains.java.decompiler.modules.decompiler.stats.*; +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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +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.getHiddenMembers().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)) { + + // access flags set + if (fd.hasModifier(CodeConstants.ACC_STATIC) && fd.hasModifier(CodeConstants.ACC_FINAL) && (noSynthFlag || fd.isSynthetic())) { + + // 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java new file mode 100644 index 000000000000..3807d2e80a27 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassReference14Processor.java @@ -0,0 +1,296 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.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.*; +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; + +import java.util.*; +import java.util.Map.Entry; + +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>(); + mapClassMethods(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.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + } + } + } + + private static 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 mapClassMethods(ClassNode node, Map<ClassWrapper, MethodWrapper> map) { + boolean noSynthFlag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); + + ClassWrapper wrapper = node.wrapper; + + for (MethodWrapper method : wrapper.getMethods()) { + StructMethod mt = method.methodStruct; + + if ((noSynthFlag || mt.isSynthetic()) && + mt.getDescriptor().equals("(Ljava/lang/String;)Ljava/lang/Class;") && + mt.hasModifier(CodeConstants.ACC_STATIC)) { + + RootStatement root = method.root; + if (root != null && 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))) { + map.put(wrapper, method); + break; + } + } + } + } + } + } + + // iterate nested classes + for (ClassNode nd : node.nested) { + mapClassMethods(nd, map); + } + } + + + private static boolean replaceInvocations(Exprent exprent, ClassWrapper wrapper, MethodWrapper meth) { + + boolean res = false; + + while (true) { + + 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 static 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.hasModifier(CodeConstants.ACC_STATIC) && + (fd.isSynthetic() || 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.getHiddenMembers() + .add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); // hide synthetic field + return ((ConstExprent)invexpr.getLstParameters().get(0)).getValue().toString(); + } + } + } + } + } + } + } + } + } + } + } + + return null; + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java new file mode 100644 index 000000000000..856ad225d0e1 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -0,0 +1,987 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.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.StructMember; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.attr.*; +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.*; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ClassWriter { + + 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 (cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM)) { + EnumProcessor.clearEnum(wrapper); + } + + if (DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ASSERTIONS)) { + AssertProcessor.buildAssertions(node); + } + } + + public void classLambdaToJava(ClassNode node, StringBuilder buffer, Exprent method_object, int indent) { + // get the class node with the content method + ClassNode classNode = node; + while (classNode != null && classNode.type == ClassNode.CLASS_LAMBDA) { + classNode = classNode.parent; + } + if (classNode == null) { + return; + } + + boolean lambdaToAnonymous = DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS); + + ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); + + try { + ClassWrapper wrapper = classNode.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 + buffer.append(method_object.toJava(indent)); + } + else { + // reference to a static method + buffer.append(ExprProcessor.getCastTypeName(new VarType(node.lambda_information.content_class_name, false))); + } + + buffer.append("::"); + buffer.append(node.lambda_information.content_method_name); + } + else { + // lambda method + StructMethod mt = cl.getMethod(node.lambda_information.content_method_key); + MethodWrapper methodWrapper = 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 (!lambdaToAnonymous) { + buffer.append('('); + + boolean firstParameter = 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 (!firstParameter) { + buffer.append(", "); + } + + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + firstParameter = false; + } + + index += md_content.params[i].stack_size; + } + + buffer.append(") ->"); + } + + buffer.append(" {"); + buffer.append(DecompilerContext.getNewLineSeparator()); + + methodLambdaToJava(node, classNode, mt, buffer, indent + 1, !lambdaToAnonymous); + + InterpreterUtil.appendIndent(buffer, indent); + buffer.append("}"); + } + } + finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode); + } + + DecompilerContext.getLogger().endWriteClass(); + } + + public void classToJava(ClassNode node, StringBuilder buffer, int indent) { + ClassNode outerNode = (ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, node); + + try { + // last minute processing + invokeProcessors(node); + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + DecompilerContext.getLogger().startWriteClass(cl.qualifiedName); + + String lineSeparator = DecompilerContext.getNewLineSeparator(); + + writeClassDefinition(node, buffer, indent); + + boolean hasContent = false; + + // fields + boolean enumFields = false; + + for (StructField fd : cl.getFields()) { + boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); + if (hide) continue; + + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + if (isEnum) { + if (enumFields) { + buffer.append(','); + buffer.append(lineSeparator); + } + enumFields = true; + } + else if (enumFields) { + buffer.append(';'); + buffer.append(lineSeparator); + buffer.append(lineSeparator); + enumFields = false; + } + + fieldToJava(wrapper, cl, fd, buffer, indent + 1); + + hasContent = true; + } + + if (enumFields) { + buffer.append(';'); + buffer.append(lineSeparator); + } + + // methods + for (StructMethod mt : cl.getMethods()) { + boolean hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) || + wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); + if (hide) continue; + + int position = buffer.length(); + if (hasContent) { + buffer.append(lineSeparator); + } + boolean methodSkipped = !methodToJava(node, mt, buffer, indent + 1); + if (!methodSkipped) { + hasContent = true; + } + else { + buffer.setLength(position); + } + } + + // member classes + for (ClassNode inner : node.nested) { + if (inner.type == ClassNode.CLASS_MEMBER) { + StructClass innerCl = inner.classStruct; + boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic(); + boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) || + wrapper.getHiddenMembers().contains(innerCl.qualifiedName); + if (hide) continue; + + if (hasContent) { + buffer.append(lineSeparator); + } + classToJava(inner, buffer, indent + 1); + + hasContent = true; + } + } + + InterpreterUtil.appendIndent(buffer, indent); + buffer.append('}'); + + if (node.type != ClassNode.CLASS_ANONYMOUS) { + buffer.append(lineSeparator); + } + } + finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_NODE, outerNode); + } + + DecompilerContext.getLogger().endWriteClass(); + } + + private void writeClassDefinition(ClassNode node, StringBuilder buffer, int indent) { + String lineSeparator = DecompilerContext.getNewLineSeparator(); + String indentString = InterpreterUtil.getIndentString(indent); + + if (node.type == ClassNode.CLASS_ANONYMOUS) { + buffer.append(" {"); + buffer.append(lineSeparator); + return; + } + + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + + int flags = node.type == ClassNode.CLASS_ROOT ? cl.getAccessFlags() : node.access; + boolean isDeprecated = cl.getAttributes().containsKey("Deprecated"); + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || cl.getAttributes().containsKey("Synthetic"); + boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; + boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0; + boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0; + + if (isDeprecated) { + appendDeprecation(buffer, indentString, lineSeparator); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName); + appendRenameComment(buffer, oldName, MType.CLASS, indent, lineSeparator); + } + + if (isSynthetic) { + appendComment(buffer, "synthetic class", indentString, lineSeparator); + } + + appendAnnotations(buffer, cl, indent, lineSeparator); + + buffer.append(indentString); + + if (isEnum) { + // remove abstract and final flags (JLS 8.9 Enums) + flags &= ~CodeConstants.ACC_ABSTRACT; + flags &= ~CodeConstants.ACC_FINAL; + } + + appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED); + + if (isEnum) { + buffer.append("enum "); + } + else if (isInterface) { + if (isAnnotation) { + buffer.append('@'); + } + buffer.append("interface "); + } + else { + buffer.append("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()); + } + } + + buffer.append(node.simpleName); + + if (descriptor != null && !descriptor.fparameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); + } + + buffer.append(' '); + + if (!isEnum && !isInterface && cl.superClass != null) { + VarType supertype = new VarType(cl.superClass.getString(), true); + if (!VarType.VARTYPE_OBJECT.equals(supertype)) { + buffer.append("extends "); + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.superclass)); + } + else { + buffer.append(ExprProcessor.getCastTypeName(supertype)); + } + buffer.append(' '); + } + } + + if (!isAnnotation) { + int[] interfaces = cl.getInterfaces(); + if (interfaces.length > 0) { + buffer.append(isInterface ? "extends " : "implements "); + for (int i = 0; i < interfaces.length; i++) { + if (i > 0) { + buffer.append(", "); + } + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.superinterfaces.get(i))); + } + else { + buffer.append(ExprProcessor.getCastTypeName(new VarType(cl.getInterface(i), true))); + } + } + buffer.append(' '); + } + } + + buffer.append('{'); + buffer.append(lineSeparator); + } + + private void fieldToJava(ClassWrapper wrapper, StructClass cl, StructField fd, StringBuilder buffer, int indent) { + String indentString = InterpreterUtil.getIndentString(indent); + String lineSeparator = DecompilerContext.getNewLineSeparator(); + + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isDeprecated = fd.getAttributes().containsKey("Deprecated"); + boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + + if (isDeprecated) { + appendDeprecation(buffer, indentString, lineSeparator); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + fd.getName() + " " + fd.getDescriptor()); + appendRenameComment(buffer, oldName, MType.FIELD, indent, lineSeparator); + } + + if (fd.isSynthetic()) { + appendComment(buffer, "synthetic field", indentString, lineSeparator); + } + + appendAnnotations(buffer, fd, indent, lineSeparator); + + buffer.append(indentString); + + if (!isEnum) { + appendModifiers(buffer, fd.getAccessFlags(), FIELD_ALLOWED, isInterface, FIELD_EXCLUDED); + } + + 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) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.type)); + } + else { + buffer.append(ExprProcessor.getCastTypeName(fieldType)); + } + buffer.append(' '); + } + + buffer.append(fd.getName()); + + Exprent initializer; + if (fd.hasModifier(CodeConstants.ACC_STATIC)) { + 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); + buffer.append(nexpr.toJava(indent)); + } + else { + buffer.append(" = "); + buffer.append(initializer.toJava(indent)); + } + } + else if (fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_STATIC)) { + StructConstantValueAttribute attr = + (StructConstantValueAttribute)fd.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_CONSTANT_VALUE); + if (attr != null) { + PrimitiveConstant constant = cl.getPool().getPrimitiveConstant(attr.getIndex()); + buffer.append(" = "); + buffer.append(new ConstExprent(fieldType, constant.value).toJava(indent)); + } + } + + if (!isEnum) { + buffer.append(";"); + buffer.append(lineSeparator); + } + } + + private static void methodLambdaToJava(ClassNode lambdaNode, + ClassNode classNode, + StructMethod mt, + StringBuilder buffer, + int indent, + boolean codeOnly) { + ClassWrapper classWrapper = classNode.wrapper; + MethodWrapper methodWrapper = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + + try { + String method_name = lambdaNode.lambda_information.method_name; + MethodDescriptor md_content = MethodDescriptor.parseDescriptor(lambdaNode.lambda_information.content_method_descriptor); + MethodDescriptor md_lambda = MethodDescriptor.parseDescriptor(lambdaNode.lambda_information.method_descriptor); + + if (!codeOnly) { + InterpreterUtil.appendIndent(buffer, indent); + buffer.append("public "); + buffer.append(method_name); + buffer.append("("); + + boolean firstParameter = true; + int index = lambdaNode.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 (!firstParameter) { + buffer.append(", "); + } + + String typeName = ExprProcessor.getCastTypeName(md_content.params[i].copy()); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + buffer.append(typeName); + buffer.append(" "); + + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + firstParameter = false; + } + + index += md_content.params[i].stack_size; + } + + buffer.append(") {"); + buffer.append(DecompilerContext.getNewLineSeparator()); + + indent += 1; + } + + if (!methodWrapper.decompiledWithErrors) { + RootStatement root = classWrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + if (root != null) { // check for existence + try { + buffer.append(root.toJava(indent)); + } + catch (Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written.", ex); + methodWrapper.decompiledWithErrors = true; + } + } + } + + if (methodWrapper.decompiledWithErrors) { + InterpreterUtil.appendIndent(buffer, indent); + buffer.append("// $FF: Couldn't be decompiled"); + buffer.append(DecompilerContext.getNewLineSeparator()); + } + + if (!codeOnly) { + indent -= 1; + + InterpreterUtil.appendIndent(buffer, indent); + buffer.append('}'); + buffer.append(DecompilerContext.getNewLineSeparator()); + } + } + finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } + } + + private boolean methodToJava(ClassNode node, StructMethod mt, StringBuilder buffer, int indent) { + ClassWrapper wrapper = node.wrapper; + StructClass cl = wrapper.getClassStruct(); + MethodWrapper methodWrapper = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()); + + boolean hideMethod = false; + + MethodWrapper outerWrapper = (MethodWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_METHOD_WRAPPER); + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, methodWrapper); + + try { + boolean isInterface = cl.hasModifier(CodeConstants.ACC_INTERFACE); + boolean isAnnotation = cl.hasModifier(CodeConstants.ACC_ANNOTATION); + boolean isEnum = cl.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM); + boolean isDeprecated = mt.getAttributes().containsKey("Deprecated"); + boolean clinit = false, init = false, dinit = false; + + String indentString = InterpreterUtil.getIndentString(indent); + String lineSeparator = DecompilerContext.getNewLineSeparator(); + + MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); + + 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; // ignore all modifiers except 'static' in a static initializer + } + + if (isDeprecated) { + appendDeprecation(buffer, indentString, lineSeparator); + } + + if (interceptor != null) { + String oldName = interceptor.getOldName(cl.qualifiedName + " " + mt.getName() + " " + mt.getDescriptor()); + appendRenameComment(buffer, oldName, MType.METHOD, indent, lineSeparator); + } + + boolean isSynthetic = (flags & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic"); + boolean isBridge = (flags & CodeConstants.ACC_BRIDGE) != 0; + if (isSynthetic) { + appendComment(buffer, "synthetic method", indentString, lineSeparator); + } + if (isBridge) { + appendComment(buffer, "bridge method", indentString, lineSeparator); + } + + appendAnnotations(buffer, mt, indent, lineSeparator); + + buffer.append(indentString); + + appendModifiers(buffer, flags, METHOD_ALLOWED, isInterface, METHOD_EXCLUDED); + + if (isInterface && mt.containsCode()) { + // 'default' modifier (Java 8) + buffer.append("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()); + if (descriptor != null) { + int actualParams = md.params.length; + if (isEnum && init) actualParams -= 2; + if (actualParams != descriptor.params.size()) { + String message = "Inconsistent generic signature in method " + mt.getName() + " " + mt.getDescriptor(); + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + descriptor = null; + } + } + } + } + + boolean throwsExceptions = false; + int paramCount = 0; + + if (!clinit && !dinit) { + boolean thisVar = !mt.hasModifier(CodeConstants.ACC_STATIC); + + if (descriptor != null && !descriptor.fparameters.isEmpty()) { + appendTypeParameters(buffer, descriptor.fparameters, descriptor.fbounds); + buffer.append(' '); + } + + if (!init) { + if (descriptor != null) { + buffer.append(GenericMain.getGenericCastTypeName(descriptor.ret)); + } + else { + buffer.append(ExprProcessor.getCastTypeName(md.ret)); + } + buffer.append(' '); + } + + buffer.append(name); + buffer.append('('); + + // parameters + List<VarVersionPaar> signFields = methodWrapper.signatureFields; + + int lastVisibleParameterIndex = -1; + for (int i = 0; i < md.params.length; i++) { + if (signFields == null || signFields.get(i) == null) { + lastVisibleParameterIndex = i; + } + } + + boolean firstParameter = 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 (!firstParameter) { + buffer.append(", "); + } + + appendParameterAnnotations(buffer, mt, paramCount); + + if (methodWrapper.varproc.getVarFinal(new VarVersionPaar(index, 0)) == VarTypeProcessor.VAR_FINALEXPLICIT) { + buffer.append("final "); + } + + if (descriptor != null) { + GenericType parameterType = descriptor.params.get(i); + + boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); + if (isVarArg) { + parameterType.arraydim--; + } + + String typeName = GenericMain.getGenericCastTypeName(parameterType); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + buffer.append(typeName); + + if (isVarArg) { + buffer.append("..."); + } + } + else { + VarType parameterType = md.params[i].copy(); + + boolean isVarArg = (i == lastVisibleParameterIndex && mt.hasModifier(CodeConstants.ACC_VARARGS) && parameterType.arraydim > 0); + if (isVarArg) { + parameterType.decArrayDim(); + } + + String typeName = ExprProcessor.getCastTypeName(parameterType); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeName) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeName = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT); + } + + buffer.append(typeName); + + if (isVarArg) { + buffer.append("..."); + } + } + + buffer.append(' '); + String parameterName = methodWrapper.varproc.getVarName(new VarVersionPaar(index, 0)); + buffer.append(parameterName == null ? "param" + index : parameterName); // null iff decompiled with errors + + firstParameter = false; + paramCount++; + } + + index += md.params[i].stack_size; + } + + buffer.append(')'); + + StructExceptionsAttribute attr = (StructExceptionsAttribute)mt.getAttributes().getWithKey("Exceptions"); + if ((descriptor != null && !descriptor.exceptions.isEmpty()) || attr != null) { + throwsExceptions = true; + buffer.append(" throws "); + + for (int i = 0; i < attr.getThrowsExceptions().size(); i++) { + if (i > 0) { + buffer.append(", "); + } + if (descriptor != null && !descriptor.exceptions.isEmpty()) { + GenericType type = descriptor.exceptions.get(i); + buffer.append(GenericMain.getGenericCastTypeName(type)); + } + else { + VarType type = new VarType(attr.getExcClassname(i, cl.getPool()), true); + buffer.append(ExprProcessor.getCastTypeName(type)); + } + } + } + } + + 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) { + buffer.append(" default "); + buffer.append(attr.getDefaultValue().toJava(indent + 1)); + } + } + + buffer.append(';'); + buffer.append(lineSeparator); + } + else { + if (!clinit && !dinit) { + buffer.append(' '); + } + + buffer.append('{'); + buffer.append(lineSeparator); + + RootStatement root = wrapper.getMethodWrapper(mt.getName(), mt.getDescriptor()).root; + + if (root != null && !methodWrapper.decompiledWithErrors) { // check for existence + try { + String code = root.toJava(indent + 1); + + hideMethod = (clinit || dinit || hideConstructor(wrapper, init, throwsExceptions, paramCount)) && code.length() == 0; + + buffer.append(code); + } + catch (Throwable ex) { + DecompilerContext.getLogger().writeMessage("Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be written.", ex); + methodWrapper.decompiledWithErrors = true; + } + } + + if (methodWrapper.decompiledWithErrors) { + buffer.append(InterpreterUtil.getIndentString(indent + 1)); + buffer.append("// $FF: Couldn't be decompiled"); + buffer.append(lineSeparator); + } + + buffer.append(indentString); + buffer.append('}'); + buffer.append(lineSeparator); + } + } + finally { + DecompilerContext.setProperty(DecompilerContext.CURRENT_METHOD_WRAPPER, outerWrapper); + } + + return !hideMethod; + } + + private static boolean hideConstructor(ClassWrapper wrapper, boolean init, boolean throwsExceptions, int paramCount) { + if (!init || throwsExceptions || paramCount > 0 || !DecompilerContext.getOption(IFernflowerPreferences.HIDE_DEFAULT_CONSTRUCTOR)) { + return false; + } + + int count = 0; + for (StructMethod mt : wrapper.getClassStruct().getMethods()) { + if ("<init>".equals(mt.getName())) { + if (++count > 1) { + return false; + } + } + } + + return true; + } + + private static void appendDeprecation(StringBuilder buffer, String indentString, String lineSeparator) { + buffer.append(indentString).append("/** @deprecated */").append(lineSeparator); + } + + private enum MType {CLASS, FIELD, METHOD} + + private static void appendRenameComment(StringBuilder buffer, String oldName, MType type, int indent, String lineSeparator) { + if (oldName == null) return; + + InterpreterUtil.appendIndent(buffer, indent); + buffer.append("// $FF: renamed from: "); + + switch (type) { + case CLASS: + buffer.append(ExprProcessor.buildJavaClassName(oldName)); + break; + + case FIELD: + String[] fParts = oldName.split(" "); + FieldDescriptor fd = FieldDescriptor.parseDescriptor(fParts[2]); + buffer.append(fParts[1]); + buffer.append(' '); + buffer.append(getTypePrintOut(fd.type)); + break; + + default: + String[] mParts = oldName.split(" "); + MethodDescriptor md = MethodDescriptor.parseDescriptor(mParts[2]); + buffer.append(mParts[1]); + buffer.append(" ("); + boolean first = true; + for (VarType paramType : md.params) { + if (!first) { + buffer.append(", "); + } + first = false; + buffer.append(getTypePrintOut(paramType)); + } + buffer.append(") "); + buffer.append(getTypePrintOut(md.ret)); + } + + buffer.append(lineSeparator); + } + + private static String getTypePrintOut(VarType type) { + String typeText = ExprProcessor.getCastTypeName(type, false); + if (ExprProcessor.UNDEFINED_TYPE_STRING.equals(typeText) && + DecompilerContext.getOption(IFernflowerPreferences.UNDEFINED_PARAM_TYPE_OBJECT)) { + typeText = ExprProcessor.getCastTypeName(VarType.VARTYPE_OBJECT, false); + } + return typeText; + } + + private static void appendComment(StringBuilder buffer, String comment, String indentString, String lineSeparator) { + buffer.append(indentString).append("// $FF: ").append(comment).append(lineSeparator); + } + + private static final String[] ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_ANNOTATIONS}; + + private static void appendAnnotations(StringBuilder buffer, StructMember mb, int indent, String lineSeparator) { + for (String name : ANNOTATION_ATTRIBUTES) { + StructAnnotationAttribute attribute = (StructAnnotationAttribute)mb.getAttributes().getWithKey(name); + if (attribute != null) { + for (AnnotationExprent annotation : attribute.getAnnotations()) { + buffer.append(annotation.toJava(indent)).append(lineSeparator); + } + } + } + } + + private static final String[] PARAMETER_ANNOTATION_ATTRIBUTES = { + StructGeneralAttribute.ATTRIBUTE_RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, StructGeneralAttribute.ATTRIBUTE_RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS}; + + private static void appendParameterAnnotations(StringBuilder buffer, StructMethod mt, int param) { + for (String name : PARAMETER_ANNOTATION_ATTRIBUTES) { + StructAnnotationParameterAttribute attribute = (StructAnnotationParameterAttribute)mt.getAttributes().getWithKey(name); + if (attribute != null) { + List<List<AnnotationExprent>> annotations = attribute.getParamAnnotations(); + if (param < annotations.size()) { + for (AnnotationExprent annotation : annotations.get(param)) { + buffer.append(annotation.toJava(0)).append(' '); + } + } + } + } + } + + private static final Map<Integer, String> MODIFIERS = new LinkedHashMap<Integer, String>() {{ + put(CodeConstants.ACC_PUBLIC, "public"); + put(CodeConstants.ACC_PROTECTED, "protected"); + put(CodeConstants.ACC_PRIVATE, "private"); + put(CodeConstants.ACC_ABSTRACT, "abstract"); + put(CodeConstants.ACC_STATIC, "static"); + put(CodeConstants.ACC_FINAL, "final"); + put(CodeConstants.ACC_STRICT, "strictfp"); + put(CodeConstants.ACC_TRANSIENT, "transient"); + put(CodeConstants.ACC_VOLATILE, "volatile"); + put(CodeConstants.ACC_SYNCHRONIZED, "synchronized"); + put(CodeConstants.ACC_NATIVE, "native"); + }}; + + private static final int CLASS_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_ABSTRACT | + CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL | CodeConstants.ACC_STRICT; + private static final int FIELD_ALLOWED = + CodeConstants.ACC_PUBLIC | CodeConstants.ACC_PROTECTED | CodeConstants.ACC_PRIVATE | CodeConstants.ACC_STATIC | + CodeConstants.ACC_FINAL | CodeConstants.ACC_TRANSIENT | CodeConstants.ACC_VOLATILE; + private static final int METHOD_ALLOWED = + 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 int CLASS_EXCLUDED = CodeConstants.ACC_ABSTRACT | CodeConstants.ACC_STATIC; + private static final int FIELD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_STATIC | CodeConstants.ACC_FINAL; + private static final int METHOD_EXCLUDED = CodeConstants.ACC_PUBLIC | CodeConstants.ACC_ABSTRACT; + + private static void appendModifiers(StringBuilder buffer, int flags, int allowed, boolean isInterface, int excluded) { + flags &= allowed; + if (!isInterface) excluded = 0; + for (int modifier : MODIFIERS.keySet()) { + if ((flags & modifier) == modifier && (modifier & excluded) == 0) { + buffer.append(MODIFIERS.get(modifier)).append(' '); + } + } + } + + private static void appendTypeParameters(StringBuilder buffer, List<String> parameters, List<List<GenericType>> bounds) { + buffer.append('<'); + + for (int i = 0; i < parameters.size(); i++) { + if (i > 0) { + buffer.append(", "); + } + + buffer.append(parameters.get(i)); + + List<GenericType> parameterBounds = bounds.get(i); + if (parameterBounds.size() > 1 || !"java/lang/Object".equals(parameterBounds.get(0).value)) { + buffer.append(" extends "); + buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(0))); + for (int j = 1; j < parameterBounds.size(); j++) { + buffer.append(" & "); + buffer.append(GenericMain.getGenericCastTypeName(parameterBounds.get(j))); + } + } + } + + buffer.append('>'); + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java new file mode 100644 index 000000000000..6faad08ae09e --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/ClassesProcessor.java @@ -0,0 +1,431 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main; + +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; + +import java.io.IOException; +import java.util.*; +import java.util.Map.Entry; + +public class ClassesProcessor { + + private Map<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; + 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.Severity.WARN); + } + } + + // 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.getAccessFlags(); + 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.Severity.WARN); + 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.Severity.WARN); + } + 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(StructClass cl, StringBuilder buffer) throws IOException { + ClassNode root = mapRootClasses.get(cl.qualifiedName); + if (root.type != ClassNode.CLASS_ROOT) { + return; + } + + try { + ImportCollector importCollector = new ImportCollector(root); + DecompilerContext.setImportCollector(importCollector); + DecompilerContext.setCounterContainer(new CounterContainer()); + + new LambdaProcessor().processClass(root); + + // add simple class names to implicit import + addClassnameToImport(root, importCollector); + + // build wrappers for all nested classes (that's where actual processing takes place) + initWrappers(root); + + new NestedClassProcessor().processClass(root, root); + + new NestedMemberAccess().propagateMemberAccess(root); + + StringBuilder classBuffer = new StringBuilder(); + new ClassWriter().classToJava(root, classBuffer, 0); + + String lineSeparator = DecompilerContext.getNewLineSeparator(); + + int index = cl.qualifiedName.lastIndexOf("/"); + if (index >= 0) { + String packageName = cl.qualifiedName.substring(0, index).replace('/', '.'); + buffer.append("package "); + buffer.append(packageName); + buffer.append(";"); + buffer.append(lineSeparator); + buffer.append(lineSeparator); + } + + if (importCollector.writeImports(buffer)) { + buffer.append(lineSeparator); + } + + buffer.append(classBuffer); + } + finally { + destroyWrappers(root); + } + } + + private static 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 static 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 static void destroyWrappers(ClassNode node) { + + node.wrapper = null; + node.classStruct.releaseResources(); + + for (ClassNode nd : node.nested) { + destroyWrappers(nd); + } + } + + public Map<String, ClassNode> getMapRootClasses() { + return mapRootClasses; + } + + + public static 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_invocation_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_invocation_type = content_method_invocation_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); + if (!is_method_reference) { // content method in the same class, check synthetic flag + StructMethod mt = classStruct.getMethod(content_method_name, content_method_descriptor); + is_method_reference = !mt.isSynthetic(); // if not synthetic -> method reference + } + + lambda_information.is_method_reference = is_method_reference; + lambda_information.is_content_method_static = + (lambda_information.content_method_invocation_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 static 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_invocation_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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/DecompilerContext.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/DecompilerContext.java new file mode 100644 index 000000000000..b600250d0b78 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -0,0 +1,150 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main; + +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; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class DecompilerContext { + public static final String CURRENT_CLASS = "CURRENT_CLASS"; + public static final String CURRENT_CLASS_NODE = "CURRENT_CLASS_NODE"; + public static final String CURRENT_METHOD = "CURRENT_METHOD"; + public static final String CURRENT_METHOD_DESCRIPTOR = "CURRENT_METHOD_DESCRIPTOR"; + public static final String CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER"; + public static final String CURRENT_VAR_PROCESSOR = "CURRENT_VAR_PROCESSOR"; + + private static ThreadLocal<DecompilerContext> currentContext = new ThreadLocal<DecompilerContext>(); + + private final Map<String, Object> properties; + private StructContext structContext; + private ImportCollector importCollector; + private VarNamesCollector varNamescollector; + private CounterContainer counterContainer; + private ClassesProcessor classProcessor; + private PoolInterceptor poolInterceptor; + private IFernflowerLogger logger; + + private DecompilerContext(Map<String, Object> properties) { + this.properties = properties; + } + + public static void initContext(Map<String, Object> propertiesCustom) { + Map<String, Object> properties = new HashMap<String, Object>(IFernflowerPreferences.DEFAULTS); + if (propertiesCustom != null) { + properties.putAll(propertiesCustom); + } + currentContext.set(new DecompilerContext(properties)); + } + + 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 getImportCollector() { + return getCurrentContext().importCollector; + } + + public static void setImportCollector(ImportCollector importCollector) { + getCurrentContext().importCollector = importCollector; + } + + public static VarNamesCollector getVarNamesCollector() { + return getCurrentContext().varNamescollector; + } + + public static void setVarNamesCollector(VarNamesCollector varNamesCollector) { + getCurrentContext().varNamescollector = varNamesCollector; + } + + 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) { + if (logger != null) { + String level = (String)getProperty(IFernflowerPreferences.LOG_LEVEL); + if (level != null) { + try { + logger.setSeverity(IFernflowerLogger.Severity.valueOf(level.toUpperCase(Locale.US))); + } + catch (IllegalArgumentException ignore) { } + } + } + getCurrentContext().logger = logger; + } + + public static String getNewLineSeparator() { + return getOption(IFernflowerPreferences.NEW_LINE_SEPARATOR) ? + IFernflowerPreferences.LINE_SEPARATOR_LIN : IFernflowerPreferences.LINE_SEPARATOR_WIN; + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/EnumProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/EnumProcessor.java new file mode 100644 index 000000000000..be408e8bbc3b --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/EnumProcessor.java @@ -0,0 +1,118 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main; + +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.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.util.InterpreterUtil; + +public class EnumProcessor { + + public static void clearEnum(ClassWrapper wrapper) { + StructClass cl = wrapper.getClassStruct(); + + // hide values/valueOf methods and super() invocations + for (MethodWrapper method : wrapper.getMethods()) { + StructMethod mt = method.methodStruct; + String name = mt.getName(); + String descriptor = mt.getDescriptor(); + + if ("values".equals(name)) { + if (descriptor.equals("()[L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(name, descriptor)); + } + } + else if ("valueOf".equals(name)) { + if (descriptor.equals("(Ljava/lang/String;)L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(name, descriptor)); + } + } + else if ("<init>".equals(name)) { + Statement firstData = findFirstData(method.root); + if (firstData != null && !firstData.getExprents().isEmpty()) { + Exprent exprent = firstData.getExprents().get(0); + if (exprent.type == Exprent.EXPRENT_INVOCATION) { + InvocationExprent invexpr = (InvocationExprent)exprent; + if (isInvocationSuperConstructor(invexpr, method, wrapper)) { + firstData.getExprents().remove(0); + } + } + } + } + } + + // hide synthetic fields of enum and it's constants + for (StructField fd : cl.getFields()) { + String descriptor = fd.getDescriptor(); + if (fd.isSynthetic() && descriptor.equals("[L" + cl.qualifiedName + ";")) { + wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), descriptor)); + } + } + } + + // 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/Fernflower.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/Fernflower.java new file mode 100644 index 000000000000..62b8ee4f61f2 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -0,0 +1,94 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main; + +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.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +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; + +import java.util.Map; + +public class Fernflower implements IDecompiledData { + + private StructContext structContext; + private ClassesProcessor classesProcessor; + + public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map<String, Object> options, IFernflowerLogger logger) { + structContext = new StructContext(saver, this, new LazyLoader(provider)); + DecompilerContext.initContext(options); + DecompilerContext.setCounterContainer(new CounterContainer()); + DecompilerContext.setLogger(logger); + } + + public void decompileContext() { + if (DecompilerContext.getOption(IFernflowerPreferences.RENAME_ENTITIES)) { + new IdentifierConverter().rename(structContext); + } + + classesProcessor = new ClassesProcessor(structContext); + + DecompilerContext.setClassProcessor(classesProcessor); + DecompilerContext.setStructContext(structContext); + + structContext.saveContext(); + } + + public void clearContext() { + DecompilerContext.setCurrentContext(null); + } + + public StructContext getStructContext() { + return structContext; + } + + @Override + public String getClassEntryName(StructClass cl, String entryName) { + ClassNode node = classesProcessor.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"; + } + } + } + + @Override + public String getClassContent(StructClass cl) { + try { + StringBuilder buffer = new StringBuilder(); + classesProcessor.writeClass(cl, buffer); + return buffer.toString(); + } + catch (Throwable ex) { + DecompilerContext.getLogger().writeMessage("Class " + cl.qualifiedName + " couldn't be fully decompiled.", ex); + return null; + } + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java new file mode 100644 index 000000000000..aabea15d1ef2 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/InitializerProcessor.java @@ -0,0 +1,323 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.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.*; +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; + +import java.util.ArrayList; +import java.util.List; + + +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.hasModifier(CodeConstants.ACC_FINAL)) { + 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; + } + + while (true) { + + 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java new file mode 100644 index 000000000000..04d50a7512e1 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/CounterContainer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java new file mode 100644 index 000000000000..9b6f7edb0019 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/ImportCollector.java @@ -0,0 +1,148 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.collectors; + +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.StructContext; + +import java.util.*; +import java.util.Map.Entry; + + +public class ImportCollector { + + private static final String JAVA_LANG_PACKAGE = "java.lang"; + + private Map<String, String> mapSimpleNames = new HashMap<String, String>(); + private Set<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 { + 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 boolean writeImports(StringBuilder buffer) { + List<String> imports = packImports(); + + for (String s : imports) { + buffer.append("import "); + buffer.append(s); + buffer.append(";"); + buffer.append(DecompilerContext.getNewLineSeparator()); + } + + return imports.size() > 0; + } + + 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) { + // exclude a current class or one of the nested ones, java.lang and empty packages + if (!setNotImportedNames.contains(ent.getKey()) && + !JAVA_LANG_PACKAGE.equals(ent.getValue()) && + !ent.getValue().isEmpty()) { + res.add(ent.getValue() + "." + ent.getKey()); + } + } + + return res; + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java new file mode 100644 index 000000000000..dfde971ac628 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/collectors/VarNamesCollector.java @@ -0,0 +1,51 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java new file mode 100644 index 000000000000..07b857838e5a --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.decompiler; + +import org.jetbrains.java.decompiler.main.Fernflower; +import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider; +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class BaseDecompiler { + + private final Fernflower fernflower; + + public BaseDecompiler(IBytecodeProvider provider, IResultSaver saver, Map<String, Object> options, IFernflowerLogger logger) { + fernflower = new Fernflower(provider, saver, options, 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java new file mode 100644 index 000000000000..a486d9f83ee1 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -0,0 +1,299 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.decompiler; + +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.IFernflowerLogger; +import org.jetbrains.java.decompiler.main.extern.IResultSaver; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.io.*; +import java.util.*; +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; + +public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { + + @SuppressWarnings("UseOfSystemOutOrSystemErr") + public static void main(String[] args) { + if (args.length < 2) { + System.out.println( + "Usage: java -jar fernflower.jar [-<option>=<value>]* [<source>]+ <destination>\n" + + "Example: java -jar fernflower.jar -dgs=true c:\\my\\source\\ c:\\my.jar d:\\decompiled\\"); + return; + } + + Map<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(Locale.US); + 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()) { + System.out.println("error: no sources given"); + return; + } + + File destination = new File(args[args.length - 1]); + if (!destination.isDirectory()) { + System.out.println("error: destination '" + destination + "' is not a directory"); + return; + } + + PrintStreamLogger logger = new PrintStreamLogger(System.out); + ConsoleDecompiler decompiler = new ConsoleDecompiler(destination, mapOptions, logger); + + for (String source : lstSources) { + decompiler.addSpace(new File(source), true); + } + for (String library : lstLibraries) { + decompiler.addSpace(new File(library), false); + } + + decompiler.decompileContext(); + } + + // ******************************************************************* + // Implementation + // ******************************************************************* + + private final File root; + private final Fernflower fernflower; + private Map<String, ZipOutputStream> mapArchiveStreams = new HashMap<String, ZipOutputStream>(); + private Map<String, Set<String>> mapArchiveEntries = new HashMap<String, Set<String>>(); + + @SuppressWarnings("UseOfSystemOutOrSystemErr") + public ConsoleDecompiler(File destination, Map<String, Object> options) { + this(destination, options, new PrintStreamLogger(System.out)); + } + + protected ConsoleDecompiler(File destination, Map<String, Object> options, IFernflowerLogger logger) { + root = destination; + fernflower = new Fernflower(this, this, options, logger); + } + + public void addSpace(File file, boolean isOwn) { + fernflower.getStructContext().addSpace(file, isOwn); + } + + public void decompileContext() { + try { + fernflower.decompileContext(); + } + finally { + fernflower.clearContext(); + } + } + + // ******************************************************************* + // Interface IBytecodeProvider + // ******************************************************************* + + @Override + public byte[] getBytecode(String externalPath, String internalPath) throws IOException { + File file = new File(externalPath); + if (internalPath == null) { + return InterpreterUtil.getBytes(file); + } + else { + ZipFile archive = new ZipFile(file); + try { + ZipEntry entry = archive.getEntry(internalPath); + if (entry == null) { + throw new IOException("Entry not found: " + internalPath); + } + return InterpreterUtil.getBytes(archive, entry); + } + finally { + archive.close(); + } + } + } + + // ******************************************************************* + // Interface IResultSaver + // ******************************************************************* + + private String getAbsolutePath(String path) { + return new File(root, path).getAbsolutePath(); + } + + @Override + public void saveFolder(String path) { + File dir = new File(getAbsolutePath(path)); + if (!(dir.mkdirs() || dir.isDirectory())) { + throw new RuntimeException("Cannot create directory " + dir); + } + } + + @Override + public void copyFile(String source, String path, String entryName) { + try { + InterpreterUtil.copyFile(new File(source), new File(getAbsolutePath(path), entryName)); + } + catch (IOException ex) { + DecompilerContext.getLogger().writeMessage("Cannot copy " + source + " to " + entryName, ex); + } + } + + @Override + public void saveClassFile(String path, String qualifiedName, String entryName, String content) { + File file = new File(getAbsolutePath(path), entryName); + try { + Writer out = new OutputStreamWriter(new FileOutputStream(file), "UTF8"); + try { + out.write(content); + } + finally { + out.close(); + } + } + catch (IOException ex) { + DecompilerContext.getLogger().writeMessage("Cannot write class file " + file, ex); + } + } + + @Override + public void createArchive(String path, String archiveName, Manifest manifest) { + File file = new File(getAbsolutePath(path), archiveName); + try { + if (!(file.createNewFile() || file.isFile())) { + throw new IOException("Cannot create file " + file); + } + + FileOutputStream fileStream = new FileOutputStream(file); + @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") + ZipOutputStream zipStream = manifest != null ? new JarOutputStream(fileStream, manifest) : new ZipOutputStream(fileStream); + mapArchiveStreams.put(file.getPath(), zipStream); + } + catch (IOException ex) { + DecompilerContext.getLogger().writeMessage("Cannot create archive " + file, ex); + } + } + + @Override + public void saveDirEntry(String path, String archiveName, String entryName) { + saveClassEntry(path, archiveName, null, entryName, null); + } + + @Override + public void copyEntry(String source, String path, String archiveName, String entryName) { + String file = new File(getAbsolutePath(path), archiveName).getPath(); + + if (!checkEntry(entryName, file)) { + return; + } + + try { + ZipFile srcArchive = new ZipFile(new File(source)); + try { + ZipEntry entry = srcArchive.getEntry(entryName); + if (entry != null) { + InputStream in = srcArchive.getInputStream(entry); + ZipOutputStream out = mapArchiveStreams.get(file); + out.putNextEntry(new ZipEntry(entryName)); + InterpreterUtil.copyStream(in, out); + in.close(); + } + } + finally { + srcArchive.close(); + } + } + catch (IOException ex) { + String message = "Cannot copy entry " + entryName + " from " + source + " to " + file; + DecompilerContext.getLogger().writeMessage(message, ex); + } + } + + @Override + public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) { + String file = new File(getAbsolutePath(path), archiveName).getPath(); + + if (!checkEntry(entryName, file)) { + return; + } + + try { + ZipOutputStream out = mapArchiveStreams.get(file); + out.putNextEntry(new ZipEntry(entryName)); + if (content != null) { + out.write(content.getBytes("UTF-8")); + } + } + catch (IOException ex) { + String message = "Cannot write entry " + entryName + " to " + file; + DecompilerContext.getLogger().writeMessage(message, ex); + } + } + + private boolean checkEntry(String entryName, String file) { + Set<String> set = mapArchiveEntries.get(file); + if (set == null) { + mapArchiveEntries.put(file, set = new HashSet<String>()); + } + + boolean added = set.add(entryName); + if (!added) { + String message = "Zip entry " + entryName + " already exists in " + file; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN); + } + return added; + } + + @Override + public void closeArchive(String path, String archiveName) { + String file = new File(getAbsolutePath(path), archiveName).getPath(); + try { + mapArchiveEntries.remove(file); + mapArchiveStreams.remove(file).close(); + } + catch (IOException ex) { + DecompilerContext.getLogger().writeMessage("Cannot close " + file, IFernflowerLogger.Severity.WARN); + } + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java new file mode 100644 index 000000000000..7bbcc192fcd3 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/decompiler/PrintStreamLogger.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.decompiler; + +import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.io.PrintStream; + +public class PrintStreamLogger extends IFernflowerLogger { + + private final PrintStream stream; + private int indent; + + public PrintStreamLogger(PrintStream printStream) { + stream = printStream; + indent = 0; + } + + @Override + public void writeMessage(String message, Severity severity) { + if (accepts(severity)) { + stream.println(InterpreterUtil.getIndentString(indent) + severity.name() + ": " + message); + } + } + + @Override + public void writeMessage(String message, Throwable t) { + writeMessage(message, Severity.ERROR); + if (accepts(Severity.ERROR)) { + t.printStackTrace(stream); + } + } + + @Override + public void startClass(String className) { + writeMessage("Processing class " + className + " ...", Severity.INFO); + ++indent; + } + + @Override + public void endClass() { + --indent; + writeMessage("... proceeded.", Severity.INFO); + } + + @Override + public void startWriteClass(String className) { + writeMessage("Writing class " + className + " ...", Severity.INFO); + ++indent; + } + + @Override + public void endWriteClass() { + --indent; + writeMessage("... written.", Severity.INFO); + } + + @Override + public void startMethod(String methodName) { + writeMessage("Processing method " + methodName + " ...", Severity.INFO); + } + + public void endMethod() { + writeMessage("... proceeded.", Severity.INFO); + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java new file mode 100644 index 000000000000..adf32707379e --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IBytecodeProvider.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.extern; + +import java.io.IOException; + +public interface IBytecodeProvider { + byte[] getBytecode(String externalPath, String internalPath) throws IOException; +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java new file mode 100644 index 000000000000..65b2c6da8641 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerLogger.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.extern; + +public abstract class IFernflowerLogger { + + public enum Severity { + TRACE, INFO, WARN, ERROR + } + + private Severity severity = Severity.INFO; + + public boolean accepts(Severity severity) { + return severity.ordinal() >= this.severity.ordinal(); + } + + public void setSeverity(Severity severity) { + this.severity = severity; + } + + public abstract void writeMessage(String message, Severity severity); + + public abstract 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 methodName) { } + + public void endMethod() { } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java new file mode 100644 index 000000000000..69dd218a0340 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.extern; + +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public interface IFernflowerPreferences { + String REMOVE_BRIDGE = "rbr"; + String REMOVE_SYNTHETIC = "rsy"; + String DECOMPILE_INNER = "din"; + String DECOMPILE_CLASS_1_4 = "dc4"; + String DECOMPILE_ASSERTIONS = "das"; + String HIDE_EMPTY_SUPER = "hes"; + String HIDE_DEFAULT_CONSTRUCTOR = "hdc"; + String DECOMPILE_GENERIC_SIGNATURES = "dgs"; + String NO_EXCEPTIONS_RETURN = "ner"; + String DECOMPILE_ENUM = "den"; + String REMOVE_GET_CLASS_NEW = "rgn"; + String LITERALS_AS_IS = "lit"; + String BOOLEAN_TRUE_ONE = "bto"; + String ASCII_STRING_CHARACTERS = "asc"; + String SYNTHETIC_NOT_SET = "nns"; + String UNDEFINED_PARAM_TYPE_OBJECT = "uto"; + String USE_DEBUG_VAR_NAMES = "udv"; + String REMOVE_EMPTY_RANGES = "rer"; + String FINALLY_DEINLINE = "fdi"; + String IDEA_NOT_NULL_ANNOTATION = "inn"; + String LAMBDA_TO_ANONYMOUS_CLASS = "lac"; + + String LOG_LEVEL = "log"; + String MAX_PROCESSING_METHOD = "mpm"; + String RENAME_ENTITIES = "ren"; + String USER_RENAMER_CLASS = "urc"; + String NEW_LINE_SEPARATOR = "nls"; + String INDENT_STRING = "ind"; + + String LINE_SEPARATOR_WIN = "\r\n"; + String LINE_SEPARATOR_LIN = "\n"; + + Map<String, Object> DEFAULTS = Collections.unmodifiableMap(new HashMap<String, Object>() {{ + put(REMOVE_BRIDGE, "1"); + put(REMOVE_SYNTHETIC, "0"); + put(DECOMPILE_INNER, "1"); + put(DECOMPILE_CLASS_1_4, "1"); + put(DECOMPILE_ASSERTIONS, "1"); + put(HIDE_EMPTY_SUPER, "1"); + put(HIDE_DEFAULT_CONSTRUCTOR, "1"); + put(DECOMPILE_GENERIC_SIGNATURES, "0"); + put(NO_EXCEPTIONS_RETURN, "1"); + put(DECOMPILE_ENUM, "1"); + put(REMOVE_GET_CLASS_NEW, "1"); + put(LITERALS_AS_IS, "0"); + put(BOOLEAN_TRUE_ONE, "1"); + put(ASCII_STRING_CHARACTERS, "0"); + put(SYNTHETIC_NOT_SET, "1"); + put(UNDEFINED_PARAM_TYPE_OBJECT, "1"); + put(USE_DEBUG_VAR_NAMES, "1"); + put(REMOVE_EMPTY_RANGES, "1"); + put(FINALLY_DEINLINE, "1"); + put(IDEA_NOT_NULL_ANNOTATION, "1"); + put(LAMBDA_TO_ANONYMOUS_CLASS, "0"); + + put(LOG_LEVEL, IFernflowerLogger.Severity.INFO.name()); + put(MAX_PROCESSING_METHOD, "0"); + put(RENAME_ENTITIES, "0"); + put(NEW_LINE_SEPARATOR, (InterpreterUtil.IS_WINDOWS ? "0" : "1")); + put(INDENT_STRING, " "); + }}); +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java new file mode 100644 index 000000000000..fc0971e10612 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IIdentifierRenamer.java @@ -0,0 +1,35 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.extern; + + +public interface IIdentifierRenamer { + + int ELEMENT_CLASS = 1; + + int ELEMENT_FIELD = 2; + + int ELEMENT_METHOD = 3; + + + boolean toBeRenamed(int element_type, String classname, String element, String descriptor); + + String getNextClassname(String fullname, String shortname); + + String getNextFieldname(String classname, String field, String descriptor); + + String getNextMethodname(String classname, String method, String descriptor); +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java new file mode 100644 index 000000000000..1e6a7d4262a5 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/extern/IResultSaver.java @@ -0,0 +1,36 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.extern; + +import java.util.jar.Manifest; + +public interface IResultSaver { + void saveFolder(String path); + + void copyFile(String source, String path, String entryName); + + void saveClassFile(String path, String qualifiedName, String entryName, String content); + + void createArchive(String path, String archiveName, Manifest manifest); + + void saveDirEntry(String path, String archiveName, String entryName); + + void copyEntry(String source, String path, String archiveName, String entry); + + void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content); + + void closeArchive(String path, String archiveName); +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java new file mode 100644 index 000000000000..4a321799bc4b --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java @@ -0,0 +1,207 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +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; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +public class ClassWrapper { + + private StructClass classStruct; + private Set<String> hiddenMembers = 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; + } + + 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()); + } + + int maxsec = Integer.parseInt(DecompilerContext.getProperty(IFernflowerPreferences.MAX_PROCESSING_METHOD).toString()); + + for (StructMethod mt : classStruct.getMethods()) { + + DecompilerContext.getLogger().startMethod(mt.getName() + " " + mt.getDescriptor()); + + VarNamesCollector vc = new VarNamesCollector(); + DecompilerContext.setVarNamesCollector(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); + + RootStatement root = null; + + boolean isError = false; + + try { + if (mt.containsCode()) { + + if (maxsec == 0) { // blocking wait + root = MethodProcessorThread.codeToJava(mt, varproc); + } + else { + MethodProcessorThread mtproc = new MethodProcessorThread(mt, varproc, DecompilerContext.getCurrentContext()); + Thread mtthread = new Thread(mtproc); + long stopAt = System.currentTimeMillis() + maxsec * 1000; + + mtthread.start(); + + while (mtthread.isAlive()) { + + synchronized (mtproc.lock) { + mtproc.lock.wait(100); + } + + if (System.currentTimeMillis() >= stopAt) { + String message = "Processing time limit exceeded for method " + mt.getName() + ", execution interrupted."; + DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR); + killThread(mtthread); + isError = true; + break; + } + } + + if (!isError) { + root = mtproc.getResult(); + } + } + } + else { + boolean thisvar = !mt.hasModifier(CodeConstants.ACC_STATIC); + 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 (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_VAR_NAMES)) { + StructLocalVariableTableAttribute attr = (StructLocalVariableTableAttribute)mt.getAttributes().getWithKey( + StructGeneralAttribute.ATTRIBUTE_LOCAL_VARIABLE_TABLE); + + if (attr != null) { + varproc.setDebugVarNames(attr.getMapVarNames()); + } + } + + DecompilerContext.getLogger().endMethod(); + } + + DecompilerContext.getLogger().endClass(); + } + + @SuppressWarnings("deprecation") + private static void killThread(Thread thread) { + thread.stop(); + } + + 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 Set<String> getHiddenMembers() { + return hiddenMembers; + } + + public VBStyleCollection<Exprent, String> getStaticFieldInitializers() { + return staticFieldInitializers; + } + + public VBStyleCollection<Exprent, String> getDynamicFieldInitializers() { + return dynamicFieldInitializers; + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java new file mode 100644 index 000000000000..743ce1215381 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/LambdaProcessor.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +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; + +import java.io.IOException; +import java.util.*; + +public class LambdaProcessor { + + private static final String JAVAC_LAMBDA_CLASS = "java/lang/invoke/LambdaMetafactory"; + private static final String JAVAC_LAMBDA_METHOD = "metafactory"; + private static final String JAVAC_LAMBDA_METHOD_DESCRIPTOR = + "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;"; + + 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 = 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java new file mode 100644 index 000000000000..2f98cf82ce08 --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorThread.java @@ -0,0 +1,259 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +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.*; +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; + +import java.io.IOException; + +public class MethodProcessorThread implements Runnable { + + public final Object lock = new Object(); + + private final StructMethod method; + private final VarProcessor varproc; + private final DecompilerContext parentContext; + + private volatile RootStatement root; + private volatile 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 (lock) { + lock.notifyAll(); + } + } + catch (ThreadDeath ex) { + throw 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.Severity.WARN); + } + + RootStatement root = DomHelper.parseGraph(graph); + + 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()); + + while (true) { + 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; + } + } + + while (true) { + + LabelHelper.cleanUpEdges(root); + + while (true) { + + 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 getResult() throws Throwable { + Throwable t = error; + if (t != null) throw t; + return root; + } + + public Throwable getError() { + return error; + } +} diff --git a/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java new file mode 100644 index 000000000000..d36b8bc578fd --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/MethodWrapper.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +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; + +import java.util.HashSet; +import java.util.List; + + +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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java new file mode 100644 index 000000000000..2e839461125c --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedClassProcessor.java @@ -0,0 +1,1030 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +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.*; +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; + +import java.util.*; +import java.util.Map.Entry; + +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.getHiddenMembers().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 & CodeConstants.ACC_SYNTHETIC) != 0 || cl.isSynthetic()) { + child.simpleName = "SyntheticClass_" + (++synthetics); + } + else { + DecompilerContext.getLogger().writeMessage("Nameless local or member class " + cl.qualifiedName + "!", + IFernflowerLogger.Severity.WARN); + 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 static 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 static 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.Severity.WARN); + } + else if (child.type == ClassNode.CLASS_LOCAL) { + DecompilerContext.getLogger() + .writeMessage("Unreferenced local class " + child.classStruct.qualifiedName + "!", IFernflowerLogger.Severity.WARN); + } + } + } + } + } + + private static 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 static 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.Severity.WARN); + } + 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 static 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.getHiddenMembers().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 static 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 static 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 noSynthFlag = 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.hasModifier(CodeConstants.ACC_FINAL) && + (fd.isSynthetic() || (noSynthFlag && fd.hasModifier(CodeConstants.ACC_PRIVATE)))) { + field = InterpreterUtil.makeUniqueKey(left.getName(), left.getDescriptor().descriptorString); + break; + } + } + } + } + } + } + } + + return field; + } + + private static void mergeListSignatures(List<VarFieldPair> first, List<VarFieldPair> second, boolean both) { + + int i = 1; + while (true) { + 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 static 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 static 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 static 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 static 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 static 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/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java new file mode 100644 index 000000000000..7e5eaa54326a --- /dev/null +++ b/plugins/java-decompiler/engine/src/org/jetbrains/java/decompiler/main/rels/NestedMemberAccess.java @@ -0,0 +1,449 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.java.decompiler.main.rels; + +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; +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.IFernflowerPreferences; +import org.jetbrains.java.decompiler.modules.decompiler.exps.*; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; +import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectNode; +import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; +import org.jetbrains.java.decompiler.struct.StructMethod; +import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; +import org.jetbrains.java.decompiler.util.InterpreterUtil; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Map; + +public class NestedMemberAccess { + + private static final int METHOD_ACCESS_NORMAL = 1; + private static final int METHOD_ACCESS_FIELD_GET = 2; + private static final int METHOD_ACCESS_FIELD_SET = 3; + private static final int METHOD_ACCESS_METHOD = 4; + + private boolean noSynthFlag; + private Map<MethodWrapper, Integer> mapMethodType = new HashMap<MethodWrapper, Integer>(); + + + public void propagateMemberAccess(ClassNode root) { + if (root.nested.isEmpty()) { + return; + } + + noSynthFlag = 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 method : node.wrapper.getMethods()) { + computeMethodType(node, method); + } + } + + private void computeMethodType(ClassNode node, MethodWrapper method) { + int type = METHOD_ACCESS_NORMAL; + + if (method.root != null) { + DirectGraph graph = method.getOrBuildGraph(); + + StructMethod mt = method.methodStruct; + if ((noSynthFlag || mt.isSynthetic()) && mt.hasModifier(CodeConstants.ACC_STATIC)) { + 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(mt.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_FIELD_GET; + } + } + } + break; + case Exprent.EXPRENT_VAR: // qualified this + if (parcount == 1) { + // this or final variable + if (((VarExprent)exprCore).getIndex() != 0) { + type = METHOD_ACCESS_FIELD_GET; + } + } + + 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_FIELD_SET; + } + } + } + } + } + } + + + 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(mt.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_FIELD_SET; + } + } + } + } + } + } + } + } + } + } + } + } + + if (type != METHOD_ACCESS_NORMAL) { + mapMethodType.put(method, type); + } + else { + mapMethodType.remove(method); + } + } + + + 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); + } + + while (true) { + + 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 static 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_FIELD_GET: + 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_FIELD_SET: + AssignmentExprent ret; + if (source.type == Exprent.EXPRENT_EXIT) { + ExitExprent extex = (ExitExprent)source; + ret = (AssignmentExprent)extex.getValue().copy(); + } + else { + ret = (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.isSynthetic()) { + hide = false; + } + } + if (hide) { + node.wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(invexpr.getName(), invexpr.getStringDescriptor())); + } + } + + return retexprent; + } +} |