IDEA-285172 - [decompiler] - StrongConnectivityHelper refactoring
[idea/community.git] / plugins / java-decompiler / engine / src / org / jetbrains / java / decompiler / modules / decompiler / ExprProcessor.java
1 // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
2 package org.jetbrains.java.decompiler.modules.decompiler;
3
4 import org.jetbrains.java.decompiler.code.CodeConstants;
5 import org.jetbrains.java.decompiler.code.Instruction;
6 import org.jetbrains.java.decompiler.code.InstructionSequence;
7 import org.jetbrains.java.decompiler.code.cfg.BasicBlock;
8 import org.jetbrains.java.decompiler.main.DecompilerContext;
9 import org.jetbrains.java.decompiler.main.collectors.BytecodeMappingTracer;
10 import org.jetbrains.java.decompiler.modules.decompiler.exps.*;
11 import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph;
12 import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectNode;
13 import org.jetbrains.java.decompiler.modules.decompiler.sforms.FlattenStatementsHelper;
14 import org.jetbrains.java.decompiler.modules.decompiler.sforms.FlattenStatementsHelper.FinallyPathWrapper;
15 import org.jetbrains.java.decompiler.modules.decompiler.stats.*;
16 import org.jetbrains.java.decompiler.modules.decompiler.typeann.TypeAnnotationWriteHelper;
17 import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor;
18 import org.jetbrains.java.decompiler.struct.StructClass;
19 import org.jetbrains.java.decompiler.struct.StructTypePath;
20 import org.jetbrains.java.decompiler.struct.attr.StructBootstrapMethodsAttribute;
21 import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
22 import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
23 import org.jetbrains.java.decompiler.struct.consts.LinkConstant;
24 import org.jetbrains.java.decompiler.struct.consts.PooledConstant;
25 import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
26 import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor;
27 import org.jetbrains.java.decompiler.struct.gen.VarType;
28 import org.jetbrains.java.decompiler.util.TextBuffer;
29
30 import java.util.*;
31 import java.util.stream.Collectors;
32
33 public class ExprProcessor implements CodeConstants {
34   @SuppressWarnings("SpellCheckingInspection")
35   public static final String UNDEFINED_TYPE_STRING = "<undefinedtype>";
36   public static final String UNKNOWN_TYPE_STRING = "<unknown>";
37   public static final String NULL_TYPE_STRING = "<null>";
38
39   private static final Map<Integer, Integer> functionMap = Map.of(
40     opc_arraylength, FunctionExprent.FUNCTION_ARRAY_LENGTH,
41     opc_checkcast, FunctionExprent.FUNCTION_CAST,
42     opc_instanceof, FunctionExprent.FUNCTION_INSTANCEOF
43   );
44
45   private static final VarType[] constants = {
46     VarType.VARTYPE_INT, VarType.VARTYPE_FLOAT, VarType.VARTYPE_LONG, VarType.VARTYPE_DOUBLE, VarType.VARTYPE_CLASS, VarType.VARTYPE_STRING
47   };
48
49   private static final VarType[] varTypes = {
50     VarType.VARTYPE_INT, VarType.VARTYPE_LONG, VarType.VARTYPE_FLOAT, VarType.VARTYPE_DOUBLE, VarType.VARTYPE_OBJECT
51   };
52
53   private static final VarType[] arrTypes = {
54     VarType.VARTYPE_INT, VarType.VARTYPE_LONG, VarType.VARTYPE_FLOAT, VarType.VARTYPE_DOUBLE, VarType.VARTYPE_OBJECT,
55     VarType.VARTYPE_BOOLEAN, VarType.VARTYPE_CHAR, VarType.VARTYPE_SHORT
56   };
57
58   private static final int[] func1 = {
59     FunctionExprent.FUNCTION_ADD, FunctionExprent.FUNCTION_SUB, FunctionExprent.FUNCTION_MUL, FunctionExprent.FUNCTION_DIV,
60     FunctionExprent.FUNCTION_REM
61   };
62   private static final int[] func2 = {
63     FunctionExprent.FUNCTION_SHL, FunctionExprent.FUNCTION_SHR, FunctionExprent.FUNCTION_USHR, FunctionExprent.FUNCTION_AND,
64     FunctionExprent.FUNCTION_OR, FunctionExprent.FUNCTION_XOR
65   };
66   private static final int[] func3 = {
67     FunctionExprent.FUNCTION_I2L, FunctionExprent.FUNCTION_I2F, FunctionExprent.FUNCTION_I2D, FunctionExprent.FUNCTION_L2I,
68     FunctionExprent.FUNCTION_L2F, FunctionExprent.FUNCTION_L2D, FunctionExprent.FUNCTION_F2I, FunctionExprent.FUNCTION_F2L,
69     FunctionExprent.FUNCTION_F2D, FunctionExprent.FUNCTION_D2I, FunctionExprent.FUNCTION_D2L, FunctionExprent.FUNCTION_D2F,
70     FunctionExprent.FUNCTION_I2B, FunctionExprent.FUNCTION_I2C, FunctionExprent.FUNCTION_I2S
71   };
72   private static final int[] func4 = {
73     FunctionExprent.FUNCTION_LCMP, FunctionExprent.FUNCTION_FCMPL, FunctionExprent.FUNCTION_FCMPG, FunctionExprent.FUNCTION_DCMPL,
74     FunctionExprent.FUNCTION_DCMPG
75   };
76   private static final int[] func5 = {
77     IfExprent.IF_EQ, IfExprent.IF_NE, IfExprent.IF_LT, IfExprent.IF_GE, IfExprent.IF_GT, IfExprent.IF_LE
78   };
79   private static final int[] func6 = {
80     IfExprent.IF_ICMPEQ, IfExprent.IF_ICMPNE, IfExprent.IF_ICMPLT, IfExprent.IF_ICMPGE, IfExprent.IF_ICMPGT, IfExprent.IF_ICMPLE,
81     IfExprent.IF_ACMPEQ, IfExprent.IF_ACMPNE
82   };
83   private static final int[] func7 = {IfExprent.IF_NULL, IfExprent.IF_NONNULL};
84   private static final int[] func8 = {MonitorExprent.MONITOR_ENTER, MonitorExprent.MONITOR_EXIT};
85
86   private static final int[] arrTypeIds = {
87     CodeConstants.TYPE_BOOLEAN, CodeConstants.TYPE_CHAR, CodeConstants.TYPE_FLOAT, CodeConstants.TYPE_DOUBLE,
88     CodeConstants.TYPE_BYTE, CodeConstants.TYPE_SHORT, CodeConstants.TYPE_INT, CodeConstants.TYPE_LONG
89   };
90
91   private static final int[] negIfs = {
92     IfExprent.IF_NE, IfExprent.IF_EQ, IfExprent.IF_GE, IfExprent.IF_LT, IfExprent.IF_LE, IfExprent.IF_GT, IfExprent.IF_NONNULL,
93     IfExprent.IF_NULL, IfExprent.IF_ICMPNE, IfExprent.IF_ICMPEQ, IfExprent.IF_ICMPGE, IfExprent.IF_ICMPLT, IfExprent.IF_ICMPLE,
94     IfExprent.IF_ICMPGT, IfExprent.IF_ACMPNE, IfExprent.IF_ACMPEQ
95   };
96
97   private static final String[] typeNames = {"byte", "char", "double", "float", "int", "long", "short", "boolean"};
98
99   private static final String EMPTY_ENTRY_POINTS_KEY = "-";
100
101   private final MethodDescriptor methodDescriptor;
102   private final VarProcessor varProcessor;
103
104   public ExprProcessor(MethodDescriptor md, VarProcessor varProc) {
105     methodDescriptor = md;
106     varProcessor = varProc;
107   }
108
109   public void processStatement(RootStatement root, StructClass cl) {
110     FlattenStatementsHelper flattenHelper = new FlattenStatementsHelper();
111     DirectGraph dgraph = flattenHelper.buildDirectGraph(root);
112
113     // collect finally entry points
114     Set<String> setFinallyShortRangeEntryPoints = new HashSet<>();
115     for (List<FinallyPathWrapper> wrappers : dgraph.mapShortRangeFinallyPaths.values()) {
116       for (FinallyPathWrapper wrapper : wrappers) {
117         setFinallyShortRangeEntryPoints.add(wrapper.entry);
118       }
119     }
120
121     Set<String> setFinallyLongRangeEntryPaths = new HashSet<>();
122     for (List<FinallyPathWrapper> wrappers : dgraph.mapLongRangeFinallyPaths.values()) {
123       for (FinallyPathWrapper wrapper : wrappers) {
124         setFinallyLongRangeEntryPaths.add(wrapper.source + "##" + wrapper.entry);
125       }
126     }
127
128     Map<String, VarExprent> mapCatch = new HashMap<>();
129     collectCatchVars(root, flattenHelper, mapCatch);
130
131     Map<DirectNode, Map<String, PrimitiveExpressionList>> mapData = new HashMap<>();
132
133     LinkedList<DirectNode> stack = new LinkedList<>();
134     LinkedList<LinkedList<String>> stackEntryPoint = new LinkedList<>();
135
136     stack.add(dgraph.first);
137     stackEntryPoint.add(new LinkedList<>());
138
139     Map<String, PrimitiveExpressionList> map = new HashMap<>();
140     map.put(EMPTY_ENTRY_POINTS_KEY, new PrimitiveExpressionList());
141     mapData.put(dgraph.first, map);
142
143     while (!stack.isEmpty()) {
144       DirectNode node = stack.removeFirst();
145       LinkedList<String> entryPoints = stackEntryPoint.removeFirst();
146
147       PrimitiveExpressionList data;
148       if (mapCatch.containsKey(node.id)) {
149         data = getExpressionData(mapCatch.get(node.id));
150       }
151       else {
152         data = mapData.get(node).get(buildEntryPointKey(entryPoints));
153       }
154
155       BasicBlockStatement block = node.block;
156       if (block != null) {
157         processBlock(block, data, cl);
158         block.setExprents(data.getExpressions());
159       }
160
161       String currentEntrypoint = entryPoints.isEmpty() ? null : entryPoints.getLast();
162
163       for (DirectNode nd : node.successors) {
164         boolean isSuccessor = true;
165
166         if (currentEntrypoint != null && dgraph.mapLongRangeFinallyPaths.containsKey(node.id)) {
167           isSuccessor = false;
168           for (FinallyPathWrapper wrapper : dgraph.mapLongRangeFinallyPaths.get(node.id)) {
169             if (wrapper.source.equals(currentEntrypoint) && wrapper.destination.equals(nd.id)) {
170               isSuccessor = true;
171               break;
172             }
173           }
174         }
175
176         if (isSuccessor) {
177           Map<String, PrimitiveExpressionList> successorMap = mapData.computeIfAbsent(nd, k -> new HashMap<>());
178           LinkedList<String> nodeEntryPoints = new LinkedList<>(entryPoints);
179
180           if (setFinallyLongRangeEntryPaths.contains(node.id + "##" + nd.id)) {
181             nodeEntryPoints.addLast(node.id);
182           }
183           else if (!setFinallyShortRangeEntryPoints.contains(nd.id) && dgraph.mapLongRangeFinallyPaths.containsKey(node.id)) {
184             nodeEntryPoints.removeLast(); // currentEntrypoint should not be null at this point
185           }
186
187           // handling of entry point loops
188           int successorEntryIndex = nodeEntryPoints.indexOf(nd.id);
189           if (successorEntryIndex >= 0) {
190             // we are in a loop (e.g. continue in a 'finally' block): drop all entry points in the list beginning with successor index
191             for (int elementsToRemove = nodeEntryPoints.size() - successorEntryIndex; elementsToRemove > 0; elementsToRemove--) {
192               nodeEntryPoints.removeLast();
193             }
194           }
195
196           String nodeEntryKey = buildEntryPointKey(nodeEntryPoints);
197           if (!successorMap.containsKey(nodeEntryKey)) {
198             successorMap.put(nodeEntryKey, data.copy());
199             stack.add(nd);
200             stackEntryPoint.add(nodeEntryPoints);
201           }
202         }
203       }
204     }
205
206     initStatementExprents(root);
207   }
208
209   // FIXME: Ugly code, to be rewritten. A tuple class is needed.
210   private static String buildEntryPointKey(LinkedList<String> entryPoints) {
211     if (entryPoints.isEmpty()) {
212       return EMPTY_ENTRY_POINTS_KEY;
213     }
214     else if (entryPoints.size() == 1) {
215       return entryPoints.getFirst();
216     }
217     else {
218       return String.join(":", entryPoints);
219     }
220   }
221
222   private static void collectCatchVars(Statement stat, FlattenStatementsHelper flatthelper, Map<String, VarExprent> map) {
223     List<VarExprent> lst = null;
224
225     if (stat.type == Statement.TYPE_CATCH_ALL) {
226       CatchAllStatement catchall = (CatchAllStatement)stat;
227       if (!catchall.isFinally()) {
228         lst = catchall.getVars();
229       }
230     }
231     else if (stat.type == Statement.TYPE_TRY_CATCH) {
232       lst = ((CatchStatement)stat).getVars();
233     }
234
235     if (lst != null) {
236       for (int i = 1; i < stat.getStats().size(); i++) {
237         map.put(flatthelper.getMapDestinationNodes().get(stat.getStats().get(i).id)[0], lst.get(i - 1));
238       }
239     }
240
241     for (Statement st : stat.getStats()) {
242       collectCatchVars(st, flatthelper, map);
243     }
244   }
245
246   private static void initStatementExprents(Statement stat) {
247     stat.initExprents();
248
249     for (Statement st : stat.getStats()) {
250       initStatementExprents(st);
251     }
252   }
253
254   public void processBlock(BasicBlockStatement stat, PrimitiveExpressionList data, StructClass cl) {
255     ConstantPool pool = cl.getPool();
256     StructBootstrapMethodsAttribute bootstrap = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_BOOTSTRAP_METHODS);
257     BasicBlock block = stat.getBlock();
258     ExpressionStack stack = data.getStack();
259     List<Exprent> exprList = data.getExpressions();
260     InstructionSequence seq = block.getSeq();
261
262     for (int i = 0; i < seq.length(); i++) {
263       Instruction instr = seq.getInstr(i);
264       Integer offset = block.getOriginalOffset(i);
265       Set<Integer> offsets = offset >= 0 ? Set.of(offset) : null;
266
267       switch (instr.opcode) {
268         case opc_aconst_null:
269           pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_NULL, null, offsets));
270           break;
271         case opc_bipush:
272         case opc_sipush:
273           pushEx(stack, exprList, new ConstExprent(instr.operand(0), true, offsets));
274           break;
275         case opc_lconst_0:
276         case opc_lconst_1:
277           pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_LONG, (long)(instr.opcode - opc_lconst_0), offsets));
278           break;
279         case opc_fconst_0:
280         case opc_fconst_1:
281         case opc_fconst_2:
282           pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_FLOAT, (float)(instr.opcode - opc_fconst_0), offsets));
283           break;
284         case opc_dconst_0:
285         case opc_dconst_1:
286           pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_DOUBLE, (double)(instr.opcode - opc_dconst_0), offsets));
287           break;
288         case opc_ldc:
289         case opc_ldc_w:
290         case opc_ldc2_w: {
291           PooledConstant cn = pool.getConstant(instr.operand(0));
292           if (cn instanceof PrimitiveConstant) {
293             pushEx(stack, exprList, new ConstExprent(constants[cn.type - CONSTANT_Integer], ((PrimitiveConstant)cn).value, offsets));
294           }
295           else if (cn instanceof LinkConstant) {
296             //TODO: for now treat Links as Strings
297             pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_STRING, ((LinkConstant)cn).elementname, offsets));
298           }
299           break;
300         }
301         case opc_iload:
302         case opc_lload:
303         case opc_fload:
304         case opc_dload:
305         case opc_aload:
306           pushEx(stack, exprList, new VarExprent(instr.operand(0), varTypes[instr.opcode - opc_iload], varProcessor, offset));
307           break;
308         case opc_iaload:
309         case opc_laload:
310         case opc_faload:
311         case opc_daload:
312         case opc_aaload:
313         case opc_baload:
314         case opc_caload:
315         case opc_saload: {
316           Exprent index = stack.pop();
317           Exprent arr = stack.pop();
318           VarType type = instr.opcode == opc_laload ? VarType.VARTYPE_LONG : instr.opcode == opc_daload ? VarType.VARTYPE_DOUBLE : null;
319           pushEx(stack, exprList, new ArrayExprent(arr, index, arrTypes[instr.opcode - opc_iaload], offsets), type);
320           break;
321         }
322         case opc_istore:
323         case opc_lstore:
324         case opc_fstore:
325         case opc_dstore:
326         case opc_astore: {
327           Exprent value = stack.pop();
328           int varIndex = instr.operand(0);
329           VarExprent left = new VarExprent(varIndex, varTypes[instr.opcode - opc_istore], varProcessor, nextMeaningfulOffset(block, i));
330           exprList.add(new AssignmentExprent(left, value, offsets));
331           break;
332         }
333         case opc_iastore:
334         case opc_lastore:
335         case opc_fastore:
336         case opc_dastore:
337         case opc_aastore:
338         case opc_bastore:
339         case opc_castore:
340         case opc_sastore: {
341           Exprent value = stack.pop();
342           Exprent index = stack.pop();
343           Exprent array = stack.pop();
344           ArrayExprent left = new ArrayExprent(array, index, arrTypes[instr.opcode - opc_iastore], offsets);
345           exprList.add(new AssignmentExprent(left, value, offsets));
346           break;
347         }
348         case opc_iadd:
349         case opc_ladd:
350         case opc_fadd:
351         case opc_dadd:
352         case opc_isub:
353         case opc_lsub:
354         case opc_fsub:
355         case opc_dsub:
356         case opc_imul:
357         case opc_lmul:
358         case opc_fmul:
359         case opc_dmul:
360         case opc_idiv:
361         case opc_ldiv:
362         case opc_fdiv:
363         case opc_ddiv:
364         case opc_irem:
365         case opc_lrem:
366         case opc_frem:
367         case opc_drem:
368           pushEx(stack, exprList, new FunctionExprent(func1[(instr.opcode - opc_iadd) / 4], stack, offsets));
369           break;
370         case opc_ishl:
371         case opc_lshl:
372         case opc_ishr:
373         case opc_lshr:
374         case opc_iushr:
375         case opc_lushr:
376         case opc_iand:
377         case opc_land:
378         case opc_ior:
379         case opc_lor:
380         case opc_ixor:
381         case opc_lxor:
382           pushEx(stack, exprList, new FunctionExprent(func2[(instr.opcode - opc_ishl) / 2], stack, offsets));
383           break;
384         case opc_ineg:
385         case opc_lneg:
386         case opc_fneg:
387         case opc_dneg:
388           pushEx(stack, exprList, new FunctionExprent(FunctionExprent.FUNCTION_NEG, stack, offsets));
389           break;
390         case opc_iinc: {
391           VarExprent varExpr = new VarExprent(instr.operand(0), VarType.VARTYPE_INT, varProcessor);
392           int type = instr.operand(1) < 0 ? FunctionExprent.FUNCTION_SUB : FunctionExprent.FUNCTION_ADD;
393           List<Exprent> operands = Arrays.asList(varExpr.copy(), new ConstExprent(VarType.VARTYPE_INT, Math.abs(instr.operand(1)), null));
394           exprList.add(new AssignmentExprent(varExpr, new FunctionExprent(type, operands, offsets), offsets));
395           break;
396         }
397         case opc_i2l:
398         case opc_i2f:
399         case opc_i2d:
400         case opc_l2i:
401         case opc_l2f:
402         case opc_l2d:
403         case opc_f2i:
404         case opc_f2l:
405         case opc_f2d:
406         case opc_d2i:
407         case opc_d2l:
408         case opc_d2f:
409         case opc_i2b:
410         case opc_i2c:
411         case opc_i2s:
412           pushEx(stack, exprList, new FunctionExprent(func3[instr.opcode - opc_i2l], stack, offsets));
413           break;
414         case opc_lcmp:
415         case opc_fcmpl:
416         case opc_fcmpg:
417         case opc_dcmpl:
418         case opc_dcmpg:
419           pushEx(stack, exprList, new FunctionExprent(func4[instr.opcode - opc_lcmp], stack, offsets));
420           break;
421         case opc_ifeq:
422         case opc_ifne:
423         case opc_iflt:
424         case opc_ifge:
425         case opc_ifgt:
426         case opc_ifle:
427           exprList.add(new IfExprent(negIfs[func5[instr.opcode - opc_ifeq]], stack, offsets));
428           break;
429         case opc_if_icmpeq:
430         case opc_if_icmpne:
431         case opc_if_icmplt:
432         case opc_if_icmpge:
433         case opc_if_icmpgt:
434         case opc_if_icmple:
435         case opc_if_acmpeq:
436         case opc_if_acmpne:
437           exprList.add(new IfExprent(negIfs[func6[instr.opcode - opc_if_icmpeq]], stack, offsets));
438           break;
439         case opc_ifnull:
440         case opc_ifnonnull:
441           exprList.add(new IfExprent(negIfs[func7[instr.opcode - opc_ifnull]], stack, offsets));
442           break;
443         case opc_tableswitch:
444         case opc_lookupswitch:
445           exprList.add(new SwitchExprent(stack.pop(), offsets));
446           break;
447         case opc_ireturn:
448         case opc_lreturn:
449         case opc_freturn:
450         case opc_dreturn:
451         case opc_areturn:
452         case opc_return:
453         case opc_athrow:
454           exprList.add(new ExitExprent(instr.opcode == opc_athrow ? ExitExprent.EXIT_THROW : ExitExprent.EXIT_RETURN,
455                                        instr.opcode == opc_return ? null : stack.pop(),
456                                        instr.opcode == opc_athrow ? null : methodDescriptor.ret,
457                                        offsets));
458           break;
459         case opc_monitorenter:
460         case opc_monitorexit:
461           exprList.add(new MonitorExprent(func8[instr.opcode - opc_monitorenter], stack.pop(), offsets));
462           break;
463         case opc_checkcast:
464         case opc_instanceof:
465           stack.push(new ConstExprent(new VarType(pool.getPrimitiveConstant(instr.operand(0)).getString(), true), null, null));
466         case opc_arraylength:
467           pushEx(stack, exprList, new FunctionExprent(functionMap.get(instr.opcode), stack, offsets));
468           break;
469         case opc_getstatic:
470         case opc_getfield:
471           pushEx(stack, exprList,
472                  new FieldExprent(pool.getLinkConstant(instr.operand(0)), instr.opcode == opc_getstatic ? null : stack.pop(), offsets));
473           break;
474         case opc_putstatic:
475         case opc_putfield:
476           Exprent valfield = stack.pop();
477           Exprent exprfield =
478             new FieldExprent(pool.getLinkConstant(instr.operand(0)), instr.opcode == opc_putstatic ? null : stack.pop(), offsets);
479           exprList.add(new AssignmentExprent(exprfield, valfield, offsets));
480           break;
481         case opc_invokevirtual:
482         case opc_invokespecial:
483         case opc_invokestatic:
484         case opc_invokeinterface:
485         case opc_invokedynamic:
486           if (instr.opcode != opc_invokedynamic || instr.bytecodeVersion >= CodeConstants.BYTECODE_JAVA_7) {
487             LinkConstant invoke_constant = pool.getLinkConstant(instr.operand(0));
488
489             List<PooledConstant> bootstrap_arguments = null;
490             if (instr.opcode == opc_invokedynamic && bootstrap != null) {
491               bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1);
492             }
493
494             InvocationExprent exprinv = new InvocationExprent(instr.opcode, invoke_constant, bootstrap_arguments, stack, offsets);
495             if (exprinv.getDescriptor().ret.type == CodeConstants.TYPE_VOID) {
496               exprList.add(exprinv);
497             }
498             else {
499               pushEx(stack, exprList, exprinv);
500             }
501           }
502           break;
503         case opc_new:
504         case opc_anewarray:
505         case opc_multianewarray:
506           int dimensions = (instr.opcode == opc_new) ? 0 : (instr.opcode == opc_anewarray) ? 1 : instr.operand(1);
507           VarType arrType = new VarType(pool.getPrimitiveConstant(instr.operand(0)).getString(), true);
508           if (instr.opcode != opc_multianewarray) {
509             arrType = arrType.resizeArrayDim(arrType.arrayDim + dimensions);
510           }
511           pushEx(stack, exprList, new NewExprent(arrType, stack, dimensions, offsets));
512           break;
513         case opc_newarray:
514           pushEx(stack, exprList, new NewExprent(new VarType(arrTypeIds[instr.operand(0) - 4], 1), stack, 1, offsets));
515           break;
516         case opc_dup:
517           pushEx(stack, exprList, stack.getByOffset(-1).copy());
518           break;
519         case opc_dup_x1:
520           insertByOffsetEx(-2, stack, exprList, -1);
521           break;
522         case opc_dup_x2:
523           if (stack.getByOffset(-2).getExprType().stackSize == 2) {
524             insertByOffsetEx(-2, stack, exprList, -1);
525           }
526           else {
527             insertByOffsetEx(-3, stack, exprList, -1);
528           }
529           break;
530         case opc_dup2:
531           if (stack.getByOffset(-1).getExprType().stackSize == 2) {
532             pushEx(stack, exprList, stack.getByOffset(-1).copy());
533           }
534           else {
535             pushEx(stack, exprList, stack.getByOffset(-2).copy());
536             pushEx(stack, exprList, stack.getByOffset(-2).copy());
537           }
538           break;
539         case opc_dup2_x1:
540           if (stack.getByOffset(-1).getExprType().stackSize == 2) {
541             insertByOffsetEx(-2, stack, exprList, -1);
542           }
543           else {
544             insertByOffsetEx(-3, stack, exprList, -2);
545             insertByOffsetEx(-3, stack, exprList, -1);
546           }
547           break;
548         case opc_dup2_x2:
549           if (stack.getByOffset(-1).getExprType().stackSize == 2) {
550             if (stack.getByOffset(-2).getExprType().stackSize == 2) {
551               insertByOffsetEx(-2, stack, exprList, -1);
552             }
553             else {
554               insertByOffsetEx(-3, stack, exprList, -1);
555             }
556           }
557           else {
558             if (stack.getByOffset(-3).getExprType().stackSize == 2) {
559               insertByOffsetEx(-3, stack, exprList, -2);
560               insertByOffsetEx(-3, stack, exprList, -1);
561             }
562             else {
563               insertByOffsetEx(-4, stack, exprList, -2);
564               insertByOffsetEx(-4, stack, exprList, -1);
565             }
566           }
567           break;
568         case opc_swap:
569           insertByOffsetEx(-2, stack, exprList, -1);
570           stack.pop();
571           break;
572         case opc_pop:
573           stack.pop();
574           break;
575         case opc_pop2:
576           if (stack.getByOffset(-1).getExprType().stackSize == 1) {
577             // Since value at the top of the stack is a value of category 1 (JVMS9 2.11.1)
578             // we should remove one more item from the stack.
579             // See JVMS9 pop2 chapter.
580             stack.pop();
581           }
582           stack.pop();
583           break;
584       }
585     }
586   }
587
588   private static int nextMeaningfulOffset(BasicBlock block, int index) {
589     InstructionSequence seq = block.getSeq();
590     while (++index < seq.length()) {
591       switch (seq.getInstr(index).opcode) {
592         case opc_nop:
593         case opc_istore:
594         case opc_lstore:
595         case opc_fstore:
596         case opc_dstore:
597         case opc_astore:
598           continue;
599       }
600       return block.getOriginalOffset(index);
601     }
602
603     List<BasicBlock> successors = block.getSuccessors();
604     if (successors.size() == 1) {
605       return successors.get(0).getOriginalOffset(0);
606     }
607
608     return -1;
609   }
610
611   private void pushEx(ExpressionStack stack, List<Exprent> exprlist, Exprent exprent) {
612     pushEx(stack, exprlist, exprent, null);
613   }
614
615   private void pushEx(ExpressionStack stack, List<Exprent> exprlist, Exprent exprent, VarType vartype) {
616     int varindex = VarExprent.STACK_BASE + stack.size();
617     VarExprent var = new VarExprent(varindex, vartype == null ? exprent.getExprType() : vartype, varProcessor);
618     var.setStack(true);
619
620     exprlist.add(new AssignmentExprent(var, exprent, null));
621     stack.push(var.copy());
622   }
623
624   private void insertByOffsetEx(int offset, ExpressionStack stack, List<Exprent> exprlist, int copyoffset) {
625
626     int base = VarExprent.STACK_BASE + stack.size();
627
628     LinkedList<VarExprent> lst = new LinkedList<>();
629
630     for (int i = -1; i >= offset; i--) {
631       Exprent varex = stack.pop();
632       VarExprent varnew = new VarExprent(base + i + 1, varex.getExprType(), varProcessor);
633       varnew.setStack(true);
634       exprlist.add(new AssignmentExprent(varnew, varex, null));
635       lst.add(0, (VarExprent)varnew.copy());
636     }
637
638     Exprent exprent = lst.get(lst.size() + copyoffset).copy();
639     VarExprent var = new VarExprent(base + offset, exprent.getExprType(), varProcessor);
640     var.setStack(true);
641     exprlist.add(new AssignmentExprent(var, exprent, null));
642     lst.add(0, (VarExprent)var.copy());
643
644     for (VarExprent expr : lst) {
645       stack.push(expr);
646     }
647   }
648
649   public static String getTypeName(VarType type, List<TypeAnnotationWriteHelper> typePathWriteStack) {
650     return getTypeName(type, true, typePathWriteStack);
651   }
652
653   public static String getTypeName(VarType type, boolean getShort, List<TypeAnnotationWriteHelper> typePathWriteStack) {
654     int tp = type.type;
655     StringBuilder sb = new StringBuilder();
656     typePathWriteStack.removeIf(typeAnnotationWriteHelper -> {
657       StructTypePath path = typeAnnotationWriteHelper.getPaths().peek();
658       if (path == null && type.arrayDim == 0) { // nested type
659         typeAnnotationWriteHelper.writeTo(sb);
660         return true;
661       }
662       if (path != null && path.getTypePathKind() == StructTypePath.Kind.ARRAY.getOpcode() &&
663         typeAnnotationWriteHelper.getPaths().size() == type.arrayDim
664       ) {
665         typeAnnotationWriteHelper.writeTo(sb);
666         return true;
667       }
668       return false;
669     });
670     if (tp <= CodeConstants.TYPE_BOOLEAN) {
671       sb.append(typeNames[tp]);
672       return sb.toString();
673     }
674     else if (tp == CodeConstants.TYPE_UNKNOWN) {
675       sb.append(UNKNOWN_TYPE_STRING);
676       return sb.toString(); // INFO: should not occur
677     }
678     else if (tp == CodeConstants.TYPE_NULL) {
679       sb.append(NULL_TYPE_STRING);
680       return sb.toString(); // INFO: should not occur
681     }
682     else if (tp == CodeConstants.TYPE_VOID) {
683       sb.append("void");
684       return sb.toString();
685     }
686     else if (tp == CodeConstants.TYPE_OBJECT) {
687       String ret;
688       if (getShort) {
689         ret = DecompilerContext.getImportCollector().getShortName(type.value);
690       } else {
691         ret = buildJavaClassName(type.value);
692       }
693
694       if (ret == null) {
695         // FIXME: a warning should be logged
696         return UNDEFINED_TYPE_STRING;
697       }
698
699       String[] nestedClasses = ret.split("\\.");
700       for (int i = 0; i < nestedClasses.length; i++) {
701         String nestedType = nestedClasses[i];
702         if (i != 0) { // first annotation is written already
703           checkNestedTypeAnnotation(sb, typePathWriteStack);
704         }
705
706         sb.append(nestedType);
707         if (i != nestedClasses.length - 1) sb.append(".");
708       }
709
710       return sb.toString();
711     }
712
713     throw new RuntimeException("invalid type");
714   }
715
716   public static void checkNestedTypeAnnotation(StringBuilder sb, List<TypeAnnotationWriteHelper> typePathWriteStack) {
717     typePathWriteStack.removeIf(typeAnnotationWriteHelper -> {
718       StructTypePath path = typeAnnotationWriteHelper.getPaths().peek();
719       if (path != null && path.getTypePathKind() == StructTypePath.Kind.NESTED.getOpcode()) {
720         typeAnnotationWriteHelper.getPaths().pop();
721         if (typeAnnotationWriteHelper.getPaths().isEmpty()) {
722           typeAnnotationWriteHelper.writeTo(sb);
723           return true;
724         }
725       }
726       return false;
727     });
728   }
729
730   public static String getCastTypeName(VarType type, List<TypeAnnotationWriteHelper> typePathWriteStack) {
731     return getCastTypeName(type, true, typePathWriteStack);
732   }
733
734   public static String getCastTypeName(VarType type, boolean getShort, List<TypeAnnotationWriteHelper> typePathWriteStack) {
735     List<TypeAnnotationWriteHelper> arrayPaths = new ArrayList<>();
736     List<TypeAnnotationWriteHelper> notArrayPath = typePathWriteStack.stream().filter(stack -> {
737       boolean isArrayPath = stack.getPaths().size() < type.arrayDim;
738       if (stack.getPaths().size() > type.arrayDim) {
739         for (int i = 0; i < type.arrayDim; i++) {
740           stack.getPaths().poll(); // remove all trailing
741         }
742       }
743       if (isArrayPath) {
744         arrayPaths.add(stack);
745       }
746       return !isArrayPath;
747     }).collect(Collectors.toList());
748     StringBuilder sb = new StringBuilder(getTypeName(type, getShort, notArrayPath));
749     writeArray(sb, type.arrayDim, arrayPaths);
750     return sb.toString();
751   }
752
753   public static void writeArray(StringBuilder sb, int arrayDim, List<TypeAnnotationWriteHelper> typePathWriteStack) {
754     for (int i = 0; i < arrayDim; i++) {
755       var ref = new Object() {
756         boolean firstIteration = true;
757       };
758       final int it = i;
759       typePathWriteStack.removeIf(writeHelper -> {
760         if (it == writeHelper.getPaths().size()) {
761           if (ref.firstIteration) {
762             sb.append(' ');
763             ref.firstIteration = false;
764           }
765           writeHelper.writeTo(sb);
766           return true;
767         }
768         return false;
769       });
770       sb.append("[]");
771     }
772   }
773
774   public static PrimitiveExpressionList getExpressionData(VarExprent var) {
775     PrimitiveExpressionList prlst = new PrimitiveExpressionList();
776     VarExprent vartmp = new VarExprent(VarExprent.STACK_BASE, var.getExprType(), var.getProcessor());
777     vartmp.setStack(true);
778
779     prlst.getExpressions().add(new AssignmentExprent(vartmp, var.copy(), null));
780     prlst.getStack().push(vartmp.copy());
781     return prlst;
782   }
783
784   public static boolean endsWithSemicolon(Exprent expr) {
785     int type = expr.type;
786     return !(type == Exprent.EXPRENT_SWITCH ||
787              type == Exprent.EXPRENT_MONITOR ||
788              type == Exprent.EXPRENT_IF ||
789              (type == Exprent.EXPRENT_VAR && ((VarExprent)expr).isClassDef()));
790   }
791
792   private static void addDeletedGotoInstructionMapping(Statement stat, BytecodeMappingTracer tracer) {
793     if (stat instanceof BasicBlockStatement) {
794       BasicBlock block = ((BasicBlockStatement)stat).getBlock();
795       List<Integer> offsets = block.getOriginalOffsets();
796       if (!offsets.isEmpty() &&
797           offsets.size() > block.getSeq().length()) { // some instructions have been deleted, but we still have offsets
798         tracer.addMapping(offsets.get(offsets.size() - 1)); // add the last offset
799       }
800     }
801   }
802
803   public static TextBuffer jmpWrapper(Statement stat, int indent, boolean semicolon, BytecodeMappingTracer tracer) {
804     TextBuffer buf = stat.toJava(indent, tracer);
805
806     List<StatEdge> lstSuccs = stat.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL);
807     if (lstSuccs.size() == 1) {
808       StatEdge edge = lstSuccs.get(0);
809       if (edge.getType() != StatEdge.TYPE_REGULAR && edge.explicit && edge.getDestination().type != Statement.TYPE_DUMMY_EXIT) {
810         buf.appendIndent(indent);
811
812         switch (edge.getType()) {
813           case StatEdge.TYPE_BREAK:
814             addDeletedGotoInstructionMapping(stat, tracer);
815             buf.append("break");
816             break;
817           case StatEdge.TYPE_CONTINUE:
818             addDeletedGotoInstructionMapping(stat, tracer);
819             buf.append("continue");
820         }
821
822         if (edge.labeled) {
823           buf.append(" label").append(edge.closure.id.toString());
824         }
825         buf.append(";").appendLineSeparator();
826         tracer.incrementCurrentSourceLine();
827       }
828     }
829
830     if (buf.length() == 0 && semicolon) {
831       buf.appendIndent(indent).append(";").appendLineSeparator();
832       tracer.incrementCurrentSourceLine();
833     }
834
835     return buf;
836   }
837
838   public static String buildJavaClassName(String name) {
839     String res = name.replace('/', '.');
840
841     if (res.contains("$")) { // attempt to invoke foreign member
842       // classes correctly
843       StructClass cl = DecompilerContext.getStructContext().getClass(name);
844       if (cl == null || !cl.isOwn()) {
845         res = res.replace('$', '.');
846       }
847     }
848
849     return res;
850   }
851
852   public static TextBuffer listToJava(List<? extends Exprent> lst, int indent, BytecodeMappingTracer tracer) {
853     if (lst == null || lst.isEmpty()) {
854       return new TextBuffer();
855     }
856
857     TextBuffer buf = new TextBuffer();
858
859     for (Exprent expr : lst) {
860       if (buf.length() > 0 && expr.type == Exprent.EXPRENT_VAR && ((VarExprent)expr).isClassDef()) {
861         // separates local class definition from previous statements
862         buf.appendLineSeparator();
863         tracer.incrementCurrentSourceLine();
864       }
865
866       TextBuffer content = expr.toJava(indent, tracer);
867
868       if (content.length() > 0) {
869         if (expr.type != Exprent.EXPRENT_VAR || !((VarExprent)expr).isClassDef()) {
870           buf.appendIndent(indent);
871         }
872         buf.append(content);
873         if (expr.type == Exprent.EXPRENT_MONITOR && ((MonitorExprent)expr).getMonType() == MonitorExprent.MONITOR_ENTER) {
874           buf.append("{}"); // empty synchronized block
875         }
876         if (endsWithSemicolon(expr)) {
877           buf.append(";");
878         }
879         buf.appendLineSeparator();
880         tracer.incrementCurrentSourceLine();
881       }
882     }
883
884     return buf;
885   }
886
887   public static ConstExprent getDefaultArrayValue(VarType arrType) {
888     ConstExprent defaultVal;
889     if (arrType.type == CodeConstants.TYPE_OBJECT || arrType.arrayDim > 0) {
890       defaultVal = new ConstExprent(VarType.VARTYPE_NULL, null, null);
891     }
892     else if (arrType.type == CodeConstants.TYPE_FLOAT) {
893       defaultVal = new ConstExprent(VarType.VARTYPE_FLOAT, 0f, null);
894     }
895     else if (arrType.type == CodeConstants.TYPE_LONG) {
896       defaultVal = new ConstExprent(VarType.VARTYPE_LONG, 0L, null);
897     }
898     else if (arrType.type == CodeConstants.TYPE_DOUBLE) {
899       defaultVal = new ConstExprent(VarType.VARTYPE_DOUBLE, 0d, null);
900     }
901     else { // integer types
902       defaultVal = new ConstExprent(0, true, null);
903     }
904     return defaultVal;
905   }
906
907   public static boolean getCastedExprent(Exprent exprent,
908                                          VarType leftType,
909                                          TextBuffer buffer,
910                                          int indent,
911                                          boolean castNull,
912                                          BytecodeMappingTracer tracer) {
913     return getCastedExprent(exprent, leftType, buffer, indent, castNull, false, false, false, tracer);
914   }
915
916   public static boolean getCastedExprent(Exprent exprent,
917                                          VarType leftType,
918                                          TextBuffer buffer,
919                                          int indent,
920                                          boolean castNull,
921                                          boolean castAlways,
922                                          boolean castNarrowing,
923                                          boolean unbox,
924                                          BytecodeMappingTracer tracer) {
925
926     if (unbox) {
927       // "unbox" invocation parameters, e.g. 'byteSet.add((byte)123)' or 'new ShortContainer((short)813)'
928       if (exprent.type == Exprent.EXPRENT_INVOCATION && ((InvocationExprent)exprent).isBoxingCall()) {
929         InvocationExprent invocationExprent = (InvocationExprent)exprent;
930         exprent = invocationExprent.getParameters().get(0);
931         int paramType = invocationExprent.getDescriptor().params[0].type;
932         if (exprent.type == Exprent.EXPRENT_CONST && ((ConstExprent)exprent).getConstType().type != paramType) {
933           leftType = new VarType(paramType);
934         }
935       }
936     }
937
938     VarType rightType = exprent.getExprType();
939
940     boolean cast =
941       castAlways ||
942       (!leftType.isSuperset(rightType) && (rightType.equals(VarType.VARTYPE_OBJECT) || leftType.type != CodeConstants.TYPE_OBJECT)) ||
943       (castNull && rightType.type == CodeConstants.TYPE_NULL && !UNDEFINED_TYPE_STRING.equals(getTypeName(leftType, Collections.emptyList()))) ||
944       (castNarrowing && isIntConstant(exprent) && isNarrowedIntType(leftType));
945
946     boolean quote = cast && exprent.getPrecedence() >= FunctionExprent.getPrecedence(FunctionExprent.FUNCTION_CAST);
947
948     // cast instead to 'byte' / 'short' when int constant is used as a value for 'Byte' / 'Short'
949     if (castNarrowing && exprent.type == Exprent.EXPRENT_CONST && !((ConstExprent) exprent).isNull()) {
950       if (leftType.equals(VarType.VARTYPE_BYTE_OBJ)) {
951         leftType = VarType.VARTYPE_BYTE;
952       }
953       else if (leftType.equals(VarType.VARTYPE_SHORT_OBJ)) {
954         leftType = VarType.VARTYPE_SHORT;
955       }
956     }
957
958     if (cast) buffer.append('(').append(ExprProcessor.getCastTypeName(leftType, Collections.emptyList())).append(')');
959
960     if (quote) buffer.append('(');
961
962     if (exprent.type == Exprent.EXPRENT_CONST) {
963       ((ConstExprent) exprent).adjustConstType(leftType);
964     }
965
966     buffer.append(exprent.toJava(indent, tracer));
967
968     if (quote) buffer.append(')');
969
970     return cast;
971   }
972
973   private static boolean isIntConstant(Exprent exprent) {
974     if (exprent.type == Exprent.EXPRENT_CONST) {
975       switch (((ConstExprent)exprent).getConstType().type) {
976         case CodeConstants.TYPE_BYTE:
977         case CodeConstants.TYPE_BYTECHAR:
978         case CodeConstants.TYPE_SHORT:
979         case CodeConstants.TYPE_SHORTCHAR:
980         case CodeConstants.TYPE_INT:
981           return true;
982       }
983     }
984
985     return false;
986   }
987
988   private static boolean isNarrowedIntType(VarType type) {
989     return VarType.VARTYPE_INT.isStrictSuperset(type) || type.equals(VarType.VARTYPE_BYTE_OBJ) || type.equals(VarType.VARTYPE_SHORT_OBJ);
990   }
991 }