From 55beef6b7e5171c90c725e6fcdf48c9d248ff07e Mon Sep 17 00:00:00 2001 From: Stiver Date: Mon, 28 Jul 2014 20:13:30 +0200 Subject: [PATCH] Fixed 'IDEA-127301: NPE in decompiler' - a couple of issues with lambda processing --- src/de/fernflower/main/ClassWriter.java | 13 +- src/de/fernflower/main/ClassesProcessor.java | 27 +- .../fernflower/main/rels/LambdaProcessor.java | 3 +- .../main/rels/MethodProcessorThread.java | 2 +- .../main/rels/NestedClassProcessor.java | 4 +- .../modules/decompiler/ExprProcessor.java | 54 +++- .../modules/decompiler/FinallyProcessor.java | 2 +- .../decompiler/SimplifyExprentsHelper.java | 5 +- .../decompiler/exps/InvocationExprent.java | 11 +- .../modules/decompiler/exps/NewExprent.java | 232 +++++++++--------- 10 files changed, 212 insertions(+), 141 deletions(-) diff --git a/src/de/fernflower/main/ClassWriter.java b/src/de/fernflower/main/ClassWriter.java index 8e569d7..7a5a2f0 100644 --- a/src/de/fernflower/main/ClassWriter.java +++ b/src/de/fernflower/main/ClassWriter.java @@ -112,7 +112,7 @@ public class ClassWriter { } - public void classLambdaToJava(ClassNode node, BufferedWriter writer, int indent) throws IOException { + public void classLambdaToJava(ClassNode node, BufferedWriter writer, Exprent method_object, int indent) throws IOException { // get the class node with the content method ClassNode node_content = node; @@ -136,7 +136,12 @@ public class ClassWriter { if(node.lambda_information.is_method_reference) { - writer.write(ExprProcessor.getCastTypeName(new VarType(node.lambda_information.content_class_name, false))); + if(!node.lambda_information.is_content_method_static && method_object != null) { // reference to a virtual method + writer.write(method_object.toJava(indent)); + } else { // reference to a static method + writer.write(ExprProcessor.getCastTypeName(new VarType(node.lambda_information.content_class_name, false))); + } + writer.write("::"); writer.write(node.lambda_information.content_method_name); @@ -156,7 +161,7 @@ public class ClassWriter { StringBuilder buff = new StringBuilder("("); boolean firstpar = true; - int index = 1; + int index = node.lambda_information.is_content_method_static ? 0 : 1;; int start_index = md_content.params.length - md_lambda.params.length; @@ -604,7 +609,7 @@ public class ClassWriter { bufstrwriter.write("("); boolean firstpar = true; - int index = 1; + int index = node_lambda.lambda_information.is_content_method_static ? 0 : 1;; int start_index = md_content.params.length - md_lambda.params.length; diff --git a/src/de/fernflower/main/ClassesProcessor.java b/src/de/fernflower/main/ClassesProcessor.java index 7f85696..9d761d2 100644 --- a/src/de/fernflower/main/ClassesProcessor.java +++ b/src/de/fernflower/main/ClassesProcessor.java @@ -379,8 +379,8 @@ public class ClassesProcessor { public LambdaInformation lambda_information; - public ClassNode(String content_class_name, String content_method_name, String content_method_descriptor, String lambda_class_name, String lambda_method_name, - String lambda_method_descriptor, StructClass classStruct) { // lambda class constructor + public ClassNode(String content_class_name, String content_method_name, String content_method_descriptor, int content_method_invokation_type, + String lambda_class_name, String lambda_method_name, String lambda_method_descriptor, StructClass classStruct) { // lambda class constructor this.type = CLASS_LAMBDA; this.classStruct = classStruct; // 'parent' class containing the static function @@ -393,19 +393,22 @@ public class ClassesProcessor { lambda_information.content_class_name = content_class_name; lambda_information.content_method_name = content_method_name; lambda_information.content_method_descriptor = content_method_descriptor; + lambda_information.content_method_invokation_type = content_method_invokation_type; + lambda_information.content_method_key = InterpreterUtil.makeUniqueKey(lambda_information.content_method_name, lambda_information.content_method_descriptor); anonimousClassType = new VarType(lambda_class_name, true); - if(content_class_name != classStruct.qualifiedName) { // method reference. FIXME: class name alone doesn't cover it. Synthetic flag seems to be the only 'reliable' difference. - lambda_information.is_method_reference = true; - lambda_information.is_content_method_static = true; // FIXME: consider argument flag - } else { - StructMethod mt = classStruct.getMethod(content_method_name, content_method_descriptor); - - lambda_information.is_method_reference = false; - lambda_information.is_content_method_static = ((mt.getAccessFlags() & CodeConstants.ACC_STATIC) != 0); + boolean is_method_reference = (content_class_name != classStruct.qualifiedName); + StructMethod mt = null; + + if(!is_method_reference) { // content method in the same class, check synthetic flag + mt = classStruct.getMethod(content_method_name, content_method_descriptor); + is_method_reference = !((mt.getAccessFlags() & CodeConstants.ACC_SYNTHETIC) != 0 || mt.getAttributes().containsKey("Synthetic")); // if not synthetic -> method reference } + + lambda_information.is_method_reference = is_method_reference; + lambda_information.is_content_method_static = (lambda_information.content_method_invokation_type == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic); // FIXME: redundant? } public ClassNode(int type, StructClass classStruct) { @@ -425,6 +428,9 @@ public class ClassesProcessor { } public class LambdaInformation { + + + public String class_name; public String method_name; public String method_descriptor; @@ -432,6 +438,7 @@ public class ClassesProcessor { public String content_class_name; public String content_method_name; public String content_method_descriptor; + public int content_method_invokation_type; // values from CONSTANT_MethodHandle_REF_* public String content_method_key; diff --git a/src/de/fernflower/main/rels/LambdaProcessor.java b/src/de/fernflower/main/rels/LambdaProcessor.java index 6e404ae..8e94646 100644 --- a/src/de/fernflower/main/rels/LambdaProcessor.java +++ b/src/de/fernflower/main/rels/LambdaProcessor.java @@ -98,7 +98,8 @@ public class LambdaProcessor { LinkConstant content_method_handle = (LinkConstant)bootstrap_arguments.get(1); - ClassNode node_lambda = clprocessor.new ClassNode(content_method_handle.classname, content_method_handle.elementname, content_method_handle.descriptor, + ClassNode node_lambda = clprocessor.new ClassNode(content_method_handle.classname, content_method_handle.elementname, + content_method_handle.descriptor, content_method_handle.index1, lambda_class_name, lambda_method_name, lambda_method_descriptor, cl); node_lambda.simpleName = cl.qualifiedName + "##Lambda_" + invoke_dynamic.index1 + "_" + invoke_dynamic.index2; node_lambda.enclosingMethod = InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()); diff --git a/src/de/fernflower/main/rels/MethodProcessorThread.java b/src/de/fernflower/main/rels/MethodProcessorThread.java index df5c925..976c838 100644 --- a/src/de/fernflower/main/rels/MethodProcessorThread.java +++ b/src/de/fernflower/main/rels/MethodProcessorThread.java @@ -175,7 +175,7 @@ public class MethodProcessorThread implements Runnable { ClearStructHelper.clearStatements(root); ExprProcessor proc = new ExprProcessor(); - proc.processStatement(root, cl.getPool()); + proc.processStatement(root, cl); // DotExporter.toDotFile(graph, new File("c:\\Temp\\fern3.dot"), true); // System.out.println(graph.toString()); diff --git a/src/de/fernflower/main/rels/NestedClassProcessor.java b/src/de/fernflower/main/rels/NestedClassProcessor.java index 32b0161..b785a75 100644 --- a/src/de/fernflower/main/rels/NestedClassProcessor.java +++ b/src/de/fernflower/main/rels/NestedClassProcessor.java @@ -56,8 +56,8 @@ public class NestedClassProcessor { public void processClass(ClassNode root, ClassNode node) { - // hide lambda content methods - if(node.type == ClassNode.CLASS_LAMBDA) { + // hide synthetic lambda content methods + if(node.type == ClassNode.CLASS_LAMBDA && !node.lambda_information.is_method_reference) { ClassNode node_content = DecompilerContext.getClassprocessor().getMapRootClasses().get(node.classStruct.qualifiedName); if(node_content != null && node_content.wrapper != null) { node_content.wrapper.getHideMembers().add(node.lambda_information.content_method_key); diff --git a/src/de/fernflower/modules/decompiler/ExprProcessor.java b/src/de/fernflower/modules/decompiler/ExprProcessor.java index 60ecc86..7bc1bef 100644 --- a/src/de/fernflower/modules/decompiler/ExprProcessor.java +++ b/src/de/fernflower/modules/decompiler/ExprProcessor.java @@ -14,22 +14,48 @@ package de.fernflower.modules.decompiler; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; import de.fernflower.code.CodeConstants; import de.fernflower.code.Instruction; import de.fernflower.code.InstructionSequence; import de.fernflower.code.cfg.BasicBlock; import de.fernflower.main.DecompilerContext; -import de.fernflower.modules.decompiler.exps.*; +import de.fernflower.modules.decompiler.exps.ArrayExprent; +import de.fernflower.modules.decompiler.exps.AssignmentExprent; +import de.fernflower.modules.decompiler.exps.ConstExprent; +import de.fernflower.modules.decompiler.exps.ExitExprent; +import de.fernflower.modules.decompiler.exps.Exprent; +import de.fernflower.modules.decompiler.exps.FieldExprent; +import de.fernflower.modules.decompiler.exps.FunctionExprent; +import de.fernflower.modules.decompiler.exps.IfExprent; +import de.fernflower.modules.decompiler.exps.InvocationExprent; +import de.fernflower.modules.decompiler.exps.MonitorExprent; +import de.fernflower.modules.decompiler.exps.NewExprent; +import de.fernflower.modules.decompiler.exps.SwitchExprent; +import de.fernflower.modules.decompiler.exps.VarExprent; import de.fernflower.modules.decompiler.sforms.DirectGraph; import de.fernflower.modules.decompiler.sforms.DirectNode; import de.fernflower.modules.decompiler.sforms.FlattenStatementsHelper; import de.fernflower.modules.decompiler.sforms.FlattenStatementsHelper.FinallyPathWrapper; -import de.fernflower.modules.decompiler.stats.*; +import de.fernflower.modules.decompiler.stats.BasicBlockStatement; +import de.fernflower.modules.decompiler.stats.CatchAllStatement; +import de.fernflower.modules.decompiler.stats.CatchStatement; +import de.fernflower.modules.decompiler.stats.RootStatement; +import de.fernflower.modules.decompiler.stats.Statement; import de.fernflower.modules.decompiler.vars.VarProcessor; import de.fernflower.struct.StructClass; +import de.fernflower.struct.attr.StructBootstrapMethodsAttribute; +import de.fernflower.struct.attr.StructGeneralAttribute; import de.fernflower.struct.consts.ConstantPool; +import de.fernflower.struct.consts.LinkConstant; +import de.fernflower.struct.consts.PooledConstant; import de.fernflower.struct.consts.PrimitiveConstant; import de.fernflower.struct.gen.MethodDescriptor; import de.fernflower.struct.gen.VarType; @@ -124,7 +150,7 @@ public class ExprProcessor implements CodeConstants { private VarProcessor varProcessor = (VarProcessor) DecompilerContext.getProperty(DecompilerContext.CURRENT_VAR_PROCESSOR); - public void processStatement(RootStatement root, ConstantPool pool) { + public void processStatement(RootStatement root, StructClass cl) { FlattenStatementsHelper flatthelper = new FlattenStatementsHelper(); DirectGraph dgraph = flatthelper.buildDirectGraph(root); @@ -179,7 +205,7 @@ public class ExprProcessor implements CodeConstants { BasicBlockStatement block = node.block; if (block != null) { - processBlock(block, data, pool); + processBlock(block, data, cl); block.setExprents(data.getLstExprents()); } @@ -291,8 +317,11 @@ public class ExprProcessor implements CodeConstants { } } - public void processBlock(BasicBlockStatement stat, PrimitiveExprsList data, ConstantPool pool) { + public void processBlock(BasicBlockStatement stat, PrimitiveExprsList data, StructClass cl) { + ConstantPool pool = cl.getPool(); + StructBootstrapMethodsAttribute bootstrap = (StructBootstrapMethodsAttribute)cl.getAttributes().getWithKey(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS); + BasicBlock block = stat.getBlock(); ExprentStack stack = data.getStack(); @@ -516,7 +545,18 @@ public class ExprProcessor implements CodeConstants { case opc_invokeinterface: case opc_invokedynamic: if(instr.opcode != opc_invokedynamic || instr.bytecode_version >= CodeConstants.BYTECODE_JAVA_7) { - InvocationExprent exprinv = new InvocationExprent(instr.opcode, pool.getLinkConstant(instr.getOperand(0)), stack); + + LinkConstant invoke_constant = pool.getLinkConstant(instr.getOperand(0)); + int dynamic_invokation_type = -1; + + if(instr.opcode == opc_invokedynamic && bootstrap != null) { + List bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1); + LinkConstant content_method_handle = (LinkConstant)bootstrap_arguments.get(1); + + dynamic_invokation_type = content_method_handle.index1; + } + + InvocationExprent exprinv = new InvocationExprent(instr.opcode, invoke_constant, stack, dynamic_invokation_type); if (exprinv.getDescriptor().ret.type == CodeConstants.TYPE_VOID) { exprlist.add(exprinv); } else { diff --git a/src/de/fernflower/modules/decompiler/FinallyProcessor.java b/src/de/fernflower/modules/decompiler/FinallyProcessor.java index cf10f48..432e26b 100644 --- a/src/de/fernflower/modules/decompiler/FinallyProcessor.java +++ b/src/de/fernflower/modules/decompiler/FinallyProcessor.java @@ -206,7 +206,7 @@ public class FinallyProcessor { } ExprProcessor proc = new ExprProcessor(); - proc.processStatement(root, mt.getClassStruct().getPool()); + proc.processStatement(root, mt.getClassStruct()); SSAConstructorSparseEx ssa = new SSAConstructorSparseEx(); ssa.splitVariables(root, mt); diff --git a/src/de/fernflower/modules/decompiler/SimplifyExprentsHelper.java b/src/de/fernflower/modules/decompiler/SimplifyExprentsHelper.java index 49db596..522e4cb 100644 --- a/src/de/fernflower/modules/decompiler/SimplifyExprentsHelper.java +++ b/src/de/fernflower/modules/decompiler/SimplifyExprentsHelper.java @@ -720,7 +720,10 @@ public class SimplifyExprentsHelper { if(lambda_class != null) { // real lambda class found, replace invocation with an anonymous class NewExprent newexp = new NewExprent(new VarType(lambda_class_name, true), null, 0); - newexp.setConstructor(in); + newexp.setConstructor(in); + // note: we don't set the instance to null with in.setInstance(null) like it is done for a common constructor invokation + // lambda can also be a reference to a virtual method (e.g. String x; ...(x::toString);) + // in this case instance will hold the corresponding object return newexp; } diff --git a/src/de/fernflower/modules/decompiler/exps/InvocationExprent.java b/src/de/fernflower/modules/decompiler/exps/InvocationExprent.java index 1e4c596..b4aeb0e 100644 --- a/src/de/fernflower/modules/decompiler/exps/InvocationExprent.java +++ b/src/de/fernflower/modules/decompiler/exps/InvocationExprent.java @@ -76,7 +76,7 @@ public class InvocationExprent extends Exprent { public InvocationExprent() {} - public InvocationExprent(int opcode, LinkConstant cn, ListStack stack) { + public InvocationExprent(int opcode, LinkConstant cn, ListStack stack, int dynamic_invokation_type) { name = cn.elementname; classname = cn.classname; @@ -99,6 +99,7 @@ public class InvocationExprent extends Exprent { classname = "java/lang/Class"; // dummy class name invoke_dynamic_classsuffix = "##Lambda_" + cn.index1 + "_" + cn.index2; + } if("".equals(name)) { @@ -114,7 +115,13 @@ public class InvocationExprent extends Exprent { lstParameters.add(0, stack.pop()); } - if(opcode == CodeConstants.opc_invokestatic || opcode == CodeConstants.opc_invokedynamic) { + if(opcode == CodeConstants.opc_invokedynamic) { + if(dynamic_invokation_type == CodeConstants.CONSTANT_MethodHandle_REF_invokeStatic) { + isStatic = true; + } else { + instance = lstParameters.get(0); // FIXME: remove the first parameter completely from the list. It's the object type for a virtual lambda method. + } + } else if(opcode == CodeConstants.opc_invokestatic) { isStatic = true; } else { instance = stack.pop(); diff --git a/src/de/fernflower/modules/decompiler/exps/NewExprent.java b/src/de/fernflower/modules/decompiler/exps/NewExprent.java index 22ee631..1023982 100644 --- a/src/de/fernflower/modules/decompiler/exps/NewExprent.java +++ b/src/de/fernflower/modules/decompiler/exps/NewExprent.java @@ -133,6 +133,12 @@ public class NewExprent extends Exprent { List lst = new ArrayList(); if(newtype.arraydim == 0) { if(constructor != null) { + Exprent constructor_instance = constructor.getInstance(); + + if(constructor_instance != null) { // should be true only for a lambda expression with a virtual content method + lst.add(constructor_instance); + } + lst.addAll(constructor.getLstParameters()); } } else { @@ -164,22 +170,22 @@ public class NewExprent extends Exprent { public String toJava(int indent) { StringBuilder buf = new StringBuilder(); - - if(anonymous) { - - ClassNode child = DecompilerContext.getClassprocessor().getMapRootClasses().get(newtype.value); + + if (anonymous) { + + ClassNode child = DecompilerContext.getClassprocessor().getMapRootClasses().get(newtype.value); buf.append("("); - - if(!lambda && constructor != null) { - + + if (!lambda && constructor != null) { + InvocationExprent invsuper = child.superInvocation; - - ClassNode newnode = DecompilerContext.getClassprocessor().getMapRootClasses().get(invsuper.getClassname()); + + ClassNode newnode = DecompilerContext.getClassprocessor().getMapRootClasses().get(invsuper.getClassname()); List sigFields = null; - if(newnode != null) { // own class - if(newnode.wrapper != null) { + if (newnode != null) { // own class + if (newnode.wrapper != null) { sigFields = newnode.wrapper.getMethodWrapper("", invsuper.getStringDescriptor()).signatureFields; } else { if(newnode.type == ClassNode.CLASS_MEMBER && (newnode.access & CodeConstants.ACC_STATIC) == 0 && @@ -191,27 +197,28 @@ public class NewExprent extends Exprent { } boolean firstpar = true; - int start = 0, end = invsuper.getLstParameters().size(); - if(enumconst) { - start += 2; end -= 1; - } - for(int i=start;i 0 && varindex <= constructor.getLstParameters().size()) { - param = constructor.getLstParameters().get(varindex-1); + if (param.type == Exprent.EXPRENT_VAR) { + int varindex = ((VarExprent) param).getIndex(); + if (varindex > 0 && varindex <= constructor.getLstParameters().size()) { + param = constructor.getLstParameters().get(varindex - 1); } } - + StringBuilder buff = new StringBuilder(); ExprProcessor.getCastedExprent(param, invsuper.getDescriptor().params[i], buff, indent, true); - + buf.append(buff); firstpar = false; } @@ -219,161 +226,162 @@ public class NewExprent extends Exprent { } - if(!enumconst) { + if (!enumconst) { String enclosing = null; - if(!lambda && constructor != null) { + if (!lambda && constructor != null) { enclosing = getQualifiedNewInstance(child.anonimousClassType.value, constructor.getLstParameters(), indent); } - + String typename = ExprProcessor.getCastTypeName(child.anonimousClassType); - - if(enclosing != null) { + + if (enclosing != null) { ClassNode anonimousNode = DecompilerContext.getClassprocessor().getMapRootClasses().get(child.anonimousClassType.value); - if(anonimousNode != null) { - typename = anonimousNode.simpleName; + if (anonimousNode != null) { + typename = anonimousNode.simpleName; } else { - typename = typename.substring(typename.lastIndexOf('.')+1); + typename = typename.substring(typename.lastIndexOf('.') + 1); } } - buf.insert(0, "new "+typename); - - if(enclosing != null) { - buf.insert(0, enclosing+"."); + buf.insert(0, "new " + typename); + + if (enclosing != null) { + buf.insert(0, enclosing + "."); } } - + buf.append(")"); - if(enumconst && buf.length() == 2) { - buf.setLength(0); - } + if (enumconst && buf.length() == 2) { + buf.setLength(0); + } StringWriter strwriter = new StringWriter(); BufferedWriter bufstrwriter = new BufferedWriter(strwriter); - + ClassWriter clwriter = new ClassWriter(); try { - if(lambda) { - clwriter.classLambdaToJava(child, bufstrwriter, indent); + if (lambda) { + clwriter.classLambdaToJava(child, bufstrwriter, (constructor == null ? null : constructor.getInstance()), indent); } else { clwriter.classToJava(child, bufstrwriter, indent); } bufstrwriter.flush(); - } catch(IOException ex) { + } catch (IOException ex) { throw new RuntimeException(ex); } - - if(lambda && !DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS)) { - buf.setLength(0); // remove the usual 'new ()', it will be replaced with lambda style '() ->' + + if (lambda && !DecompilerContext.getOption(IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS)) { + buf.setLength(0); // remove the usual 'new ()', it will + // be replaced with lambda style '() ->' } - + buf.append(strwriter.toString()); - - } else if(directArrayInit) { + + } else if (directArrayInit) { VarType leftType = newtype.copy(); leftType.decArrayDim(); - + buf.append("{"); - for(int i=0;i0) { + for(int i = 0; i < lstArrayElements.size(); i++) { + if (i > 0) { buf.append(", "); } StringBuilder buff = new StringBuilder(); ExprProcessor.getCastedExprent(lstArrayElements.get(i), leftType, buff, indent, false); - + buf.append(buff); } buf.append("}"); } else { - if(newtype.arraydim == 0) { - - if(constructor != null) { + if (newtype.arraydim == 0) { + + if (constructor != null) { List lstParameters = constructor.getLstParameters(); - - ClassNode newnode = DecompilerContext.getClassprocessor().getMapRootClasses().get(constructor.getClassname()); - + + ClassNode newnode = DecompilerContext.getClassprocessor().getMapRootClasses().get(constructor.getClassname()); + List sigFields = null; - if(newnode != null) { // own class - if(newnode.wrapper != null) { + if (newnode != null) { // own class + if (newnode.wrapper != null) { sigFields = newnode.wrapper.getMethodWrapper("", constructor.getStringDescriptor()).signatureFields; } else { - if(newnode.type == ClassNode.CLASS_MEMBER && (newnode.access & CodeConstants.ACC_STATIC) == 0 && - !constructor.getLstParameters().isEmpty()) { // member non-static class invoked with enclosing class instance - sigFields = new ArrayList(Collections.nCopies(lstParameters.size(), (VarVersionPaar)null)); + if (newnode.type == ClassNode.CLASS_MEMBER && (newnode.access & CodeConstants.ACC_STATIC) == 0 && + !constructor.getLstParameters().isEmpty()) { // member non-static class invoked with enclosing class instance + sigFields = new ArrayList(Collections.nCopies(lstParameters.size(), (VarVersionPaar) null)); sigFields.set(0, new VarVersionPaar(-1, 0)); } } } - int start = enumconst ? 2 : 0; - if(!enumconst || start < lstParameters.size()) { - buf.append("("); - - boolean firstpar = true; - for(int i=start;i0) { + + VarType leftType = newtype.copy(); + leftType.decArrayDim(); + + buf.append("{"); + for(int i = 0; i < lstArrayElements.size(); i++) { + if (i > 0) { buf.append(", "); } StringBuilder buff = new StringBuilder(); ExprProcessor.getCastedExprent(lstArrayElements.get(i), leftType, buff, indent, false); - + buf.append(buff); } buf.append("}");