IDEA-285172 - [decompiler] - StrongConnectivityHelper refactoring
[idea/community.git] / plugins / java-decompiler / engine / src / org / jetbrains / java / decompiler / modules / decompiler / SimplifyExprentsHelper.java
1 // Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package org.jetbrains.java.decompiler.modules.decompiler;
3
4 import org.jetbrains.java.decompiler.code.CodeConstants;
5 import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode;
6 import org.jetbrains.java.decompiler.main.DecompilerContext;
7 import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences;
8 import org.jetbrains.java.decompiler.main.rels.ClassWrapper;
9 import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
10 import org.jetbrains.java.decompiler.modules.decompiler.sforms.SSAConstructorSparseEx;
11 import org.jetbrains.java.decompiler.modules.decompiler.stats.IfStatement;
12 import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement;
13 import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair;
14 import org.jetbrains.java.decompiler.struct.StructClass;
15 import org.jetbrains.java.decompiler.struct.gen.VarType;
16 import org.jetbrains.java.decompiler.struct.match.MatchEngine;
17 import org.jetbrains.java.decompiler.util.FastSparseSetFactory.FastSparseSet;
18 import org.jetbrains.java.decompiler.util.InterpreterUtil;
19
20 import java.util.*;
21 import java.util.Map.Entry;
22
23 public class SimplifyExprentsHelper {
24   @SuppressWarnings("SpellCheckingInspection") private static final MatchEngine class14Builder = new MatchEngine(
25     "statement type:if iftype:if exprsize:-1\n" +
26     " exprent position:head type:if\n" +
27     "  exprent type:function functype:eq\n" +
28     "   exprent type:field name:$fieldname$\n" +
29     "   exprent type:constant consttype:null\n" +
30     " statement type:basicblock\n" +
31     "  exprent position:-1 type:assignment ret:$assignfield$\n" +
32     "   exprent type:var index:$var$\n" +
33     "   exprent type:field name:$fieldname$\n" +
34     " statement type:sequence statsize:2\n" +
35     "  statement type:trycatch\n" +
36     "   statement type:basicblock exprsize:1\n" +
37     "    exprent type:assignment\n" +
38     "     exprent type:var index:$var$\n" +
39     "     exprent type:invocation invclass:java/lang/Class signature:forName(Ljava/lang/String;)Ljava/lang/Class;\n" +
40     "      exprent position:0 type:constant consttype:string constvalue:$classname$\n" +
41     "   statement type:basicblock exprsize:1\n" +
42     "    exprent type:exit exittype:throw\n" +
43     "  statement type:basicblock exprsize:1\n" +
44     "   exprent type:assignment\n" +
45     "    exprent type:field name:$fieldname$ ret:$field$\n" +
46     "    exprent type:var index:$var$");
47
48   private final boolean firstInvocation;
49
50   public SimplifyExprentsHelper(boolean firstInvocation) {
51     this.firstInvocation = firstInvocation;
52   }
53
54   public boolean simplifyStackVarsStatement(Statement stat, Set<Integer> setReorderedIfs, SSAConstructorSparseEx ssa, StructClass cl) {
55     boolean res = false;
56
57     List<Exprent> expressions = stat.getExprents();
58     if (expressions == null) {
59       boolean processClass14 = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_CLASS_1_4);
60
61       while (true) {
62         boolean changed = false;
63
64         for (Statement st : stat.getStats()) {
65           res |= simplifyStackVarsStatement(st, setReorderedIfs, ssa, cl);
66
67           changed = IfHelper.mergeIfs(st, setReorderedIfs) ||  // collapse composed if's
68                     buildIff(st, ssa) ||  // collapse iff ?: statement
69                     processClass14 && collapseInlinedClass14(st);  // collapse inlined .class property in version 1.4 and before
70
71           if (changed) {
72             break;
73           }
74         }
75
76         res |= changed;
77
78         if (!changed) {
79           break;
80         }
81       }
82     }
83     else {
84       res = simplifyStackVarsExprents(expressions, cl);
85     }
86
87     return res;
88   }
89
90   private boolean simplifyStackVarsExprents(List<Exprent> list, StructClass cl) {
91     boolean res = false;
92
93     int index = 0;
94     while (index < list.size()) {
95       Exprent current = list.get(index);
96
97       Exprent ret = isSimpleConstructorInvocation(current);
98       if (ret != null) {
99         list.set(index, ret);
100         res = true;
101         continue;
102       }
103
104       // lambda expression (Java 8)
105       ret = isLambda(current, cl);
106       if (ret != null) {
107         list.set(index, ret);
108         res = true;
109         continue;
110       }
111
112       // remove monitor exit
113       if (isMonitorExit(current)) {
114         list.remove(index);
115         res = true;
116         continue;
117       }
118
119       // trivial assignment of a stack variable
120       if (isTrivialStackAssignment(current)) {
121         list.remove(index);
122         res = true;
123         continue;
124       }
125
126       if (index == list.size() - 1) {
127         break;
128       }
129
130       Exprent next = list.get(index + 1);
131
132       // constructor invocation
133       if (isConstructorInvocationRemote(list, index)) {
134         list.remove(index);
135         res = true;
136         continue;
137       }
138
139       // remove getClass() invocation, which is part of a qualified new
140       if (DecompilerContext.getOption(IFernflowerPreferences.REMOVE_GET_CLASS_NEW)) {
141         if (isQualifiedNewGetClass(current, next)) {
142           list.remove(index);
143           res = true;
144           continue;
145         }
146       }
147
148       // direct initialization of an array
149       int arrCount = isArrayInitializer(list, index);
150       if (arrCount > 0) {
151         for (int i = 0; i < arrCount; i++) {
152           list.remove(index + 1);
153         }
154         res = true;
155         continue;
156       }
157
158       // add array initializer expression
159       if (addArrayInitializer(current, next)) {
160         list.remove(index + 1);
161         res = true;
162         continue;
163       }
164
165       // integer ++expr and --expr  (except for vars!)
166       Exprent func = isPPIorMMI(current);
167       if (func != null) {
168         list.set(index, func);
169         res = true;
170         continue;
171       }
172
173       // expr++ and expr--
174       if (isIPPorIMM(current, next) || isIPPorIMM2(current, next)) {
175         list.remove(index + 1);
176         res = true;
177         continue;
178       }
179
180       // assignment on stack
181       if (isStackAssignment(current, next)) {
182         list.remove(index + 1);
183         res = true;
184         continue;
185       }
186
187       if (!firstInvocation && isStackAssignment2(current, next)) {
188         list.remove(index + 1);
189         res = true;
190         continue;
191       }
192
193       index++;
194     }
195
196     return res;
197   }
198
199   private static boolean addArrayInitializer(Exprent first, Exprent second) {
200     if (first.type == Exprent.EXPRENT_ASSIGNMENT) {
201       AssignmentExprent as = (AssignmentExprent)first;
202
203       if (as.getRight().type == Exprent.EXPRENT_NEW && as.getLeft().type == Exprent.EXPRENT_VAR) {
204         NewExprent newExpr = (NewExprent)as.getRight();
205
206         if (!newExpr.getLstArrayElements().isEmpty()) {
207           VarExprent arrVar = (VarExprent)as.getLeft();
208
209           if (second.type == Exprent.EXPRENT_ASSIGNMENT) {
210             AssignmentExprent aas = (AssignmentExprent)second;
211             if (aas.getLeft().type == Exprent.EXPRENT_ARRAY) {
212               ArrayExprent arrExpr = (ArrayExprent)aas.getLeft();
213               if (arrExpr.getArray().type == Exprent.EXPRENT_VAR &&
214                   arrVar.equals(arrExpr.getArray()) &&
215                   arrExpr.getIndex().type == Exprent.EXPRENT_CONST) {
216                 int constValue = ((ConstExprent)arrExpr.getIndex()).getIntValue();
217
218                 if (constValue < newExpr.getLstArrayElements().size()) {
219                   Exprent init = newExpr.getLstArrayElements().get(constValue);
220                   if (init.type == Exprent.EXPRENT_CONST) {
221                     ConstExprent cinit = (ConstExprent)init;
222                     VarType arrType = newExpr.getNewType().decreaseArrayDim();
223                     ConstExprent defaultVal = ExprProcessor.getDefaultArrayValue(arrType);
224
225                     if (cinit.equals(defaultVal)) {
226                       Exprent tempExpr = aas.getRight();
227
228                       if (!tempExpr.containsExprent(arrVar)) {
229                         newExpr.getLstArrayElements().set(constValue, tempExpr);
230
231                         if (tempExpr.type == Exprent.EXPRENT_NEW) {
232                           NewExprent tempNewExpr = (NewExprent)tempExpr;
233                           int dims = newExpr.getNewType().arrayDim;
234                           if (dims > 1 && !tempNewExpr.getLstArrayElements().isEmpty()) {
235                             tempNewExpr.setDirectArrayInit(true);
236                           }
237                         }
238
239                         return true;
240                       }
241                     }
242                   }
243                 }
244               }
245             }
246           }
247         }
248       }
249     }
250
251     return false;
252   }
253
254   private static int isArrayInitializer(List<Exprent> list, int index) {
255     Exprent current = list.get(index);
256     if (current.type == Exprent.EXPRENT_ASSIGNMENT) {
257       AssignmentExprent as = (AssignmentExprent)current;
258
259       if (as.getRight().type == Exprent.EXPRENT_NEW && as.getLeft().type == Exprent.EXPRENT_VAR) {
260         NewExprent newExpr = (NewExprent)as.getRight();
261
262         if (newExpr.getExprType().arrayDim > 0 && newExpr.getLstDims().size() == 1 && newExpr.getLstArrayElements().isEmpty() &&
263             newExpr.getLstDims().get(0).type == Exprent.EXPRENT_CONST) {
264
265           int size = (Integer)((ConstExprent)newExpr.getLstDims().get(0)).getValue();
266           if (size == 0) {
267             return 0;
268           }
269
270           VarExprent arrVar = (VarExprent)as.getLeft();
271           Map<Integer, Exprent> mapInit = new HashMap<>();
272
273           int i = 1;
274           while (index + i < list.size() && i <= size) {
275             boolean found = false;
276
277             Exprent expr = list.get(index + i);
278             if (expr.type == Exprent.EXPRENT_ASSIGNMENT) {
279               AssignmentExprent aas = (AssignmentExprent)expr;
280               if (aas.getLeft().type == Exprent.EXPRENT_ARRAY) {
281                 ArrayExprent arrExpr = (ArrayExprent)aas.getLeft();
282                 if (arrExpr.getArray().type == Exprent.EXPRENT_VAR && arrVar.equals(arrExpr.getArray()) &&
283                     arrExpr.getIndex().type == Exprent.EXPRENT_CONST) {
284                   // TODO: check for a number type. Failure extremely improbable, but nevertheless...
285                   int constValue = ((ConstExprent)arrExpr.getIndex()).getIntValue();
286                   if (constValue < size && !mapInit.containsKey(constValue)) {
287                     if (!aas.getRight().containsExprent(arrVar)) {
288                       mapInit.put(constValue, aas.getRight());
289                       found = true;
290                     }
291                   }
292                 }
293               }
294             }
295
296             if (!found) {
297               break;
298             }
299
300             i++;
301           }
302
303           double fraction = ((double)mapInit.size()) / size;
304
305           if ((arrVar.isStack() && fraction > 0) || (size <= 7 && fraction >= 0.3) || (size > 7 && fraction >= 0.7)) {
306             List<Exprent> lstRet = new ArrayList<>();
307
308             VarType arrayType = newExpr.getNewType().decreaseArrayDim();
309             ConstExprent defaultVal = ExprProcessor.getDefaultArrayValue(arrayType);
310             for (int j = 0; j < size; j++) {
311               lstRet.add(defaultVal.copy());
312             }
313
314             int dims = newExpr.getNewType().arrayDim;
315             for (Entry<Integer, Exprent> ent : mapInit.entrySet()) {
316               Exprent tempExpr = ent.getValue();
317               lstRet.set(ent.getKey(), tempExpr);
318
319               if (tempExpr.type == Exprent.EXPRENT_NEW) {
320                 NewExprent tempNewExpr = (NewExprent)tempExpr;
321                 if (dims > 1 && !tempNewExpr.getLstArrayElements().isEmpty()) {
322                   tempNewExpr.setDirectArrayInit(true);
323                 }
324               }
325             }
326
327             newExpr.setLstArrayElements(lstRet);
328
329             return mapInit.size();
330           }
331         }
332       }
333     }
334
335     return 0;
336   }
337
338   private static boolean isTrivialStackAssignment(Exprent first) {
339     if (first.type == Exprent.EXPRENT_ASSIGNMENT) {
340       AssignmentExprent asf = (AssignmentExprent)first;
341
342       if (asf.getLeft().type == Exprent.EXPRENT_VAR && asf.getRight().type == Exprent.EXPRENT_VAR) {
343         VarExprent left = (VarExprent)asf.getLeft();
344         VarExprent right = (VarExprent)asf.getRight();
345         return left.getIndex() == right.getIndex() && left.isStack() && right.isStack();
346       }
347     }
348
349     return false;
350   }
351
352   private static boolean isStackAssignment2(Exprent first, Exprent second) {  // e.g. 1.4-style class invocation
353     if (first.type == Exprent.EXPRENT_ASSIGNMENT && second.type == Exprent.EXPRENT_ASSIGNMENT) {
354       AssignmentExprent asf = (AssignmentExprent)first;
355       AssignmentExprent ass = (AssignmentExprent)second;
356
357       if (asf.getLeft().type == Exprent.EXPRENT_VAR && ass.getRight().type == Exprent.EXPRENT_VAR &&
358           asf.getLeft().equals(ass.getRight()) && ((VarExprent)asf.getLeft()).isStack()) {
359         if (ass.getLeft().type != Exprent.EXPRENT_VAR || !((VarExprent)ass.getLeft()).isStack()) {
360           asf.setRight(new AssignmentExprent(ass.getLeft(), asf.getRight(), ass.bytecode));
361           return true;
362         }
363       }
364     }
365
366     return false;
367   }
368
369   private static boolean isStackAssignment(Exprent first, Exprent second) {
370     if (first.type == Exprent.EXPRENT_ASSIGNMENT && second.type == Exprent.EXPRENT_ASSIGNMENT) {
371       AssignmentExprent asf = (AssignmentExprent)first;
372       AssignmentExprent ass = (AssignmentExprent)second;
373
374       while (true) {
375         if (asf.getRight().equals(ass.getRight())) {
376           if ((asf.getLeft().type == Exprent.EXPRENT_VAR && ((VarExprent)asf.getLeft()).isStack()) &&
377               (ass.getLeft().type != Exprent.EXPRENT_VAR || !((VarExprent)ass.getLeft()).isStack())) {
378
379             if (!ass.getLeft().containsExprent(asf.getLeft())) {
380               asf.setRight(ass);
381               return true;
382             }
383           }
384         }
385         if (asf.getRight().type == Exprent.EXPRENT_ASSIGNMENT) {
386           asf = (AssignmentExprent)asf.getRight();
387         }
388         else {
389           break;
390         }
391       }
392     }
393
394     return false;
395   }
396
397   private static Exprent isPPIorMMI(Exprent first) {
398     if (first.type == Exprent.EXPRENT_ASSIGNMENT) {
399       AssignmentExprent as = (AssignmentExprent)first;
400
401       if (as.getRight().type == Exprent.EXPRENT_FUNCTION) {
402         FunctionExprent func = (FunctionExprent)as.getRight();
403
404         if (func.getFuncType() == FunctionExprent.FUNCTION_ADD || func.getFuncType() == FunctionExprent.FUNCTION_SUB) {
405           Exprent econd = func.getLstOperands().get(0);
406           Exprent econst = func.getLstOperands().get(1);
407
408           if (econst.type != Exprent.EXPRENT_CONST && econd.type == Exprent.EXPRENT_CONST &&
409               func.getFuncType() == FunctionExprent.FUNCTION_ADD) {
410             econd = econst;
411             econst = func.getLstOperands().get(0);
412           }
413
414           if (econst.type == Exprent.EXPRENT_CONST && ((ConstExprent)econst).hasValueOne()) {
415             Exprent left = as.getLeft();
416
417             if (left.type != Exprent.EXPRENT_VAR && left.equals(econd)) {
418               int type = func.getFuncType() == FunctionExprent.FUNCTION_ADD ? FunctionExprent.FUNCTION_PPI : FunctionExprent.FUNCTION_MMI;
419               FunctionExprent ret = new FunctionExprent(type, econd, func.bytecode);
420               ret.setImplicitType(VarType.VARTYPE_INT);
421               return ret;
422             }
423           }
424         }
425       }
426     }
427
428     return null;
429   }
430
431   private static boolean isIPPorIMM(Exprent first, Exprent second) {
432     if (first.type == Exprent.EXPRENT_ASSIGNMENT && second.type == Exprent.EXPRENT_FUNCTION) {
433       AssignmentExprent as = (AssignmentExprent)first;
434       FunctionExprent in = (FunctionExprent)second;
435
436       if ((in.getFuncType() == FunctionExprent.FUNCTION_MMI || in.getFuncType() == FunctionExprent.FUNCTION_PPI) &&
437           in.getLstOperands().get(0).equals(as.getRight())) {
438
439         if (in.getFuncType() == FunctionExprent.FUNCTION_MMI) {
440           in.setFuncType(FunctionExprent.FUNCTION_IMM);
441         }
442         else {
443           in.setFuncType(FunctionExprent.FUNCTION_IPP);
444         }
445         as.setRight(in);
446
447         return true;
448       }
449     }
450
451     return false;
452   }
453
454   private static boolean isIPPorIMM2(Exprent first, Exprent second) {
455     if (first.type != Exprent.EXPRENT_ASSIGNMENT || second.type != Exprent.EXPRENT_ASSIGNMENT) {
456       return false;
457     }
458
459     AssignmentExprent af = (AssignmentExprent)first;
460     AssignmentExprent as = (AssignmentExprent)second;
461
462     if (as.getRight().type != Exprent.EXPRENT_FUNCTION) {
463       return false;
464     }
465
466     FunctionExprent func = (FunctionExprent)as.getRight();
467
468     if (func.getFuncType() != FunctionExprent.FUNCTION_ADD && func.getFuncType() != FunctionExprent.FUNCTION_SUB) {
469       return false;
470     }
471
472     Exprent econd = func.getLstOperands().get(0);
473     Exprent econst = func.getLstOperands().get(1);
474
475     if (econst.type != Exprent.EXPRENT_CONST && econd.type == Exprent.EXPRENT_CONST && func.getFuncType() == FunctionExprent.FUNCTION_ADD) {
476       econd = econst;
477       econst = func.getLstOperands().get(0);
478     }
479
480     if (econst.type == Exprent.EXPRENT_CONST &&
481         ((ConstExprent)econst).hasValueOne() &&
482         af.getLeft().equals(econd) &&
483         af.getRight().equals(as.getLeft()) &&
484         (af.getLeft().getExprentUse() & Exprent.MULTIPLE_USES) != 0) {
485       int type = func.getFuncType() == FunctionExprent.FUNCTION_ADD ? FunctionExprent.FUNCTION_IPP : FunctionExprent.FUNCTION_IMM;
486
487       FunctionExprent ret = new FunctionExprent(type, af.getRight(), func.bytecode);
488       ret.setImplicitType(VarType.VARTYPE_INT);
489
490       af.setRight(ret);
491       return true;
492     }
493
494     return false;
495   }
496   
497   private static boolean isMonitorExit(Exprent first) {
498     if (first.type == Exprent.EXPRENT_MONITOR) {
499       MonitorExprent expr = (MonitorExprent)first;
500       return expr.getMonType() == MonitorExprent.MONITOR_EXIT &&
501              expr.getValue().type == Exprent.EXPRENT_VAR &&
502              !((VarExprent)expr.getValue()).isStack();
503     }
504
505     return false;
506   }
507
508   private static boolean isQualifiedNewGetClass(Exprent first, Exprent second) {
509     if (first.type == Exprent.EXPRENT_INVOCATION) {
510       InvocationExprent invocation = (InvocationExprent)first;
511
512       if (!invocation.isStatic() && invocation.getInstance().type == Exprent.EXPRENT_VAR && invocation.getName().equals("getClass") &&
513           invocation.getStringDescriptor().equals("()Ljava/lang/Class;")) {
514
515         List<Exprent> lstExprents = second.getAllExprents();
516         lstExprents.add(second);
517
518         for (Exprent expr : lstExprents) {
519           if (expr.type == Exprent.EXPRENT_NEW) {
520             NewExprent newExpr = (NewExprent)expr;
521             if (newExpr.getConstructor() != null && !newExpr.getConstructor().getParameters().isEmpty() &&
522                 newExpr.getConstructor().getParameters().get(0).equals(invocation.getInstance())) {
523
524               String classname = newExpr.getNewType().value;
525               ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(classname);
526               if (node != null && node.type != ClassNode.CLASS_ROOT) {
527                 return true;
528               }
529             }
530           }
531         }
532       }
533     }
534
535     return false;
536   }
537
538   // propagate (var = new X) forward to the <init> invocation
539   private static boolean isConstructorInvocationRemote(List<Exprent> list, int index) {
540     Exprent current = list.get(index);
541
542     if (current.type == Exprent.EXPRENT_ASSIGNMENT) {
543       AssignmentExprent as = (AssignmentExprent)current;
544
545       if (as.getLeft().type == Exprent.EXPRENT_VAR && as.getRight().type == Exprent.EXPRENT_NEW) {
546
547         NewExprent newExpr = (NewExprent)as.getRight();
548         VarType newType = newExpr.getNewType();
549         VarVersionPair leftPair = new VarVersionPair((VarExprent)as.getLeft());
550
551         if (newType.type == CodeConstants.TYPE_OBJECT && newType.arrayDim == 0 && newExpr.getConstructor() == null) {
552           for (int i = index + 1; i < list.size(); i++) {
553             Exprent remote = list.get(i);
554
555             // <init> invocation
556             if (remote.type == Exprent.EXPRENT_INVOCATION) {
557               InvocationExprent in = (InvocationExprent)remote;
558
559               if (in.getFuncType() == InvocationExprent.TYPE_INIT &&
560                   in.getInstance().type == Exprent.EXPRENT_VAR &&
561                   as.getLeft().equals(in.getInstance())) {
562                 newExpr.setConstructor(in);
563                 in.setInstance(null);
564
565                 list.set(i, as.copy());
566
567                 return true;
568               }
569             }
570
571             // check for variable in use
572             Set<VarVersionPair> setVars = remote.getAllVariables();
573             if (setVars.contains(leftPair)) { // variable used somewhere in between -> exit, need a better reduced code
574               return false;
575             }
576           }
577         }
578       }
579     }
580
581     return false;
582   }
583
584   private static Exprent isLambda(Exprent exprent, StructClass cl) {
585     List<Exprent> lst = exprent.getAllExprents();
586     for (Exprent expr : lst) {
587       Exprent ret = isLambda(expr, cl);
588       if (ret != null) {
589         exprent.replaceExprent(expr, ret);
590       }
591     }
592
593     if (exprent.type == Exprent.EXPRENT_INVOCATION) {
594       InvocationExprent in = (InvocationExprent)exprent;
595
596       if (in.getInvocationType() == InvocationExprent.INVOKE_DYNAMIC) {
597         String lambda_class_name = cl.qualifiedName + in.getInvokeDynamicClassSuffix();
598         ClassNode lambda_class = DecompilerContext.getClassProcessor().getMapRootClasses().get(lambda_class_name);
599
600         if (lambda_class != null) { // real lambda class found, replace invocation with an anonymous class
601           NewExprent newExpr = new NewExprent(new VarType(lambda_class_name, true), null, 0, in.bytecode);
602           newExpr.setConstructor(in);
603           // note: we don't set the instance to null with in.setInstance(null) like it is done for a common constructor invocation
604           // lambda can also be a reference to a virtual method (e.g. String x; ...(x::toString);)
605           // in this case instance will hold the corresponding object
606
607           return newExpr;
608         }
609       }
610     }
611
612     return null;
613   }
614
615   private static Exprent isSimpleConstructorInvocation(Exprent exprent) {
616     List<Exprent> lst = exprent.getAllExprents();
617     for (Exprent expr : lst) {
618       Exprent ret = isSimpleConstructorInvocation(expr);
619       if (ret != null) {
620         exprent.replaceExprent(expr, ret);
621       }
622     }
623
624     if (exprent.type == Exprent.EXPRENT_INVOCATION) {
625       InvocationExprent in = (InvocationExprent)exprent;
626       if (in.getFuncType() == InvocationExprent.TYPE_INIT && in.getInstance().type == Exprent.EXPRENT_NEW) {
627         NewExprent newExpr = (NewExprent)in.getInstance();
628         newExpr.setConstructor(in);
629         in.setInstance(null);
630         return newExpr;
631       }
632     }
633
634     return null;
635   }
636
637   private static boolean buildIff(Statement stat, SSAConstructorSparseEx ssa) {
638     if (stat.type == Statement.TYPE_IF && stat.getExprents() == null) {
639       IfStatement statement = (IfStatement)stat;
640       Exprent ifHeadExpr = statement.getHeadexprent();
641       Set<Integer> ifHeadExprBytecode = (ifHeadExpr == null ? null : ifHeadExpr.bytecode);
642
643       if (statement.iftype == IfStatement.IFTYPE_IFELSE) {
644         Statement ifStatement = statement.getIfstat();
645         Statement elseStatement = statement.getElsestat();
646
647         if (ifStatement.getExprents() != null && ifStatement.getExprents().size() == 1 &&
648             elseStatement.getExprents() != null && elseStatement.getExprents().size() == 1 &&
649             ifStatement.getAllSuccessorEdges().size() == 1 && elseStatement.getAllSuccessorEdges().size() == 1 &&
650             ifStatement.getAllSuccessorEdges().get(0).getDestination() == elseStatement.getAllSuccessorEdges().get(0).getDestination()) {
651           Exprent ifExpr = ifStatement.getExprents().get(0);
652           Exprent elseExpr = elseStatement.getExprents().get(0);
653
654           if (ifExpr.type == Exprent.EXPRENT_ASSIGNMENT && elseExpr.type == Exprent.EXPRENT_ASSIGNMENT) {
655             AssignmentExprent ifAssign = (AssignmentExprent)ifExpr;
656             AssignmentExprent elseAssign = (AssignmentExprent)elseExpr;
657
658             if (ifAssign.getLeft().type == Exprent.EXPRENT_VAR && elseAssign.getLeft().type == Exprent.EXPRENT_VAR) {
659               VarExprent ifVar = (VarExprent)ifAssign.getLeft();
660               VarExprent elseVar = (VarExprent)elseAssign.getLeft();
661
662               if (ifVar.getIndex() == elseVar.getIndex() && ifVar.isStack()) { // ifVar.getIndex() >= VarExprent.STACK_BASE) {
663                 boolean found = false;
664
665                 for (Entry<VarVersionPair, FastSparseSet<Integer>> ent : ssa.getPhi().entrySet()) {
666                   if (ent.getKey().var == ifVar.getIndex()) {
667                     if (ent.getValue().contains(ifVar.getVersion()) && ent.getValue().contains(elseVar.getVersion())) {
668                       found = true;
669                       break;
670                     }
671                   }
672                 }
673
674                 if (found) {
675                   List<Exprent> data = new ArrayList<>(statement.getFirst().getExprents());
676
677                   List<Exprent> operands = Arrays.asList(statement.getHeadexprent().getCondition(), ifAssign.getRight(), elseAssign.getRight());
678                   data.add(new AssignmentExprent(ifVar, new FunctionExprent(FunctionExprent.FUNCTION_IIF, operands, ifHeadExprBytecode), ifHeadExprBytecode));
679                   statement.setExprents(data);
680
681                   if (statement.getAllSuccessorEdges().isEmpty()) {
682                     StatEdge ifEdge = ifStatement.getAllSuccessorEdges().get(0);
683                     StatEdge edge = new StatEdge(ifEdge.getType(), statement, ifEdge.getDestination());
684
685                     statement.addSuccessor(edge);
686                     if (ifEdge.closure != null) {
687                       ifEdge.closure.addLabeledEdge(edge);
688                     }
689                   }
690
691                   SequenceHelper.destroyAndFlattenStatement(statement);
692
693                   return true;
694                 }
695               }
696             }
697           }
698           else if (ifExpr.type == Exprent.EXPRENT_EXIT && elseExpr.type == Exprent.EXPRENT_EXIT) {
699             ExitExprent ifExit = (ExitExprent)ifExpr;
700             ExitExprent elseExit = (ExitExprent)elseExpr;
701
702             if (ifExit.getExitType() == elseExit.getExitType() && ifExit.getValue() != null && elseExit.getValue() != null &&
703                 ifExit.getExitType() == ExitExprent.EXIT_RETURN) {
704               // throw is dangerous, because of implicit casting to a common superclass
705               // e.g. throws IOException and throw true?new RuntimeException():new IOException(); won't work
706               if (ifExit.getExitType() == ExitExprent.EXIT_THROW &&
707                   !ifExit.getValue().getExprType().equals(elseExit.getValue().getExprType())) {  // note: getExprType unreliable at this point!
708                 return false;
709               }
710
711               // avoid flattening to 'iff' if any of the branches is an 'iff' already
712               if (isIff(ifExit.getValue()) || isIff(elseExit.getValue())) {
713                 return false;
714               }
715
716               List<Exprent> data = new ArrayList<>(statement.getFirst().getExprents());
717
718               data.add(new ExitExprent(ifExit.getExitType(), new FunctionExprent(FunctionExprent.FUNCTION_IIF,
719                                                                                Arrays.asList(
720                                                                                  statement.getHeadexprent().getCondition(),
721                                                                                  ifExit.getValue(),
722                                                                                  elseExit.getValue()), ifHeadExprBytecode), ifExit.getRetType(), ifHeadExprBytecode));
723               statement.setExprents(data);
724
725               StatEdge retEdge = ifStatement.getAllSuccessorEdges().get(0);
726               Statement closure = retEdge.closure == statement ? statement.getParent() : retEdge.closure;
727               statement.addSuccessor(new StatEdge(StatEdge.TYPE_BREAK, statement, retEdge.getDestination(), closure));
728
729               SequenceHelper.destroyAndFlattenStatement(statement);
730
731               return true;
732             }
733           }
734         }
735       }
736     }
737
738     return false;
739   }
740
741   private static boolean isIff(Exprent exp) {
742     return exp.type == Exprent.EXPRENT_FUNCTION && ((FunctionExprent) exp).getFuncType() == FunctionExprent.FUNCTION_IIF;
743   }
744
745   private static boolean collapseInlinedClass14(Statement stat) {
746     boolean ret = class14Builder.match(stat);
747     if (ret) {
748       String class_name = (String)class14Builder.getVariableValue("$classname$");
749       AssignmentExprent assignment = (AssignmentExprent)class14Builder.getVariableValue("$assignfield$");
750       FieldExprent fieldExpr = (FieldExprent)class14Builder.getVariableValue("$field$");
751
752       assignment.replaceExprent(assignment.getRight(), new ConstExprent(VarType.VARTYPE_CLASS, class_name, null));
753
754       List<Exprent> data = new ArrayList<>(stat.getFirst().getExprents());
755
756       stat.setExprents(data);
757
758       SequenceHelper.destroyAndFlattenStatement(stat);
759
760       ClassWrapper wrapper = (ClassWrapper)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_WRAPPER);
761       if (wrapper != null) {
762         wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fieldExpr.getName(), fieldExpr.getDescriptor().descriptorString));
763       }
764     }
765
766     return ret;
767   }
768 }