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;
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;
31 import java.util.stream.Collectors;
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>";
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
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
49 private static final VarType[] varTypes = {
50 VarType.VARTYPE_INT, VarType.VARTYPE_LONG, VarType.VARTYPE_FLOAT, VarType.VARTYPE_DOUBLE, VarType.VARTYPE_OBJECT
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
58 private static final int[] func1 = {
59 FunctionExprent.FUNCTION_ADD, FunctionExprent.FUNCTION_SUB, FunctionExprent.FUNCTION_MUL, FunctionExprent.FUNCTION_DIV,
60 FunctionExprent.FUNCTION_REM
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
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
72 private static final int[] func4 = {
73 FunctionExprent.FUNCTION_LCMP, FunctionExprent.FUNCTION_FCMPL, FunctionExprent.FUNCTION_FCMPG, FunctionExprent.FUNCTION_DCMPL,
74 FunctionExprent.FUNCTION_DCMPG
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
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
83 private static final int[] func7 = {IfExprent.IF_NULL, IfExprent.IF_NONNULL};
84 private static final int[] func8 = {MonitorExprent.MONITOR_ENTER, MonitorExprent.MONITOR_EXIT};
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
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
97 private static final String[] typeNames = {"byte", "char", "double", "float", "int", "long", "short", "boolean"};
99 private static final String EMPTY_ENTRY_POINTS_KEY = "-";
101 private final MethodDescriptor methodDescriptor;
102 private final VarProcessor varProcessor;
104 public ExprProcessor(MethodDescriptor md, VarProcessor varProc) {
105 methodDescriptor = md;
106 varProcessor = varProc;
109 public void processStatement(RootStatement root, StructClass cl) {
110 FlattenStatementsHelper flattenHelper = new FlattenStatementsHelper();
111 DirectGraph dgraph = flattenHelper.buildDirectGraph(root);
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);
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);
128 Map<String, VarExprent> mapCatch = new HashMap<>();
129 collectCatchVars(root, flattenHelper, mapCatch);
131 Map<DirectNode, Map<String, PrimitiveExpressionList>> mapData = new HashMap<>();
133 LinkedList<DirectNode> stack = new LinkedList<>();
134 LinkedList<LinkedList<String>> stackEntryPoint = new LinkedList<>();
136 stack.add(dgraph.first);
137 stackEntryPoint.add(new LinkedList<>());
139 Map<String, PrimitiveExpressionList> map = new HashMap<>();
140 map.put(EMPTY_ENTRY_POINTS_KEY, new PrimitiveExpressionList());
141 mapData.put(dgraph.first, map);
143 while (!stack.isEmpty()) {
144 DirectNode node = stack.removeFirst();
145 LinkedList<String> entryPoints = stackEntryPoint.removeFirst();
147 PrimitiveExpressionList data;
148 if (mapCatch.containsKey(node.id)) {
149 data = getExpressionData(mapCatch.get(node.id));
152 data = mapData.get(node).get(buildEntryPointKey(entryPoints));
155 BasicBlockStatement block = node.block;
157 processBlock(block, data, cl);
158 block.setExprents(data.getExpressions());
161 String currentEntrypoint = entryPoints.isEmpty() ? null : entryPoints.getLast();
163 for (DirectNode nd : node.successors) {
164 boolean isSuccessor = true;
166 if (currentEntrypoint != null && dgraph.mapLongRangeFinallyPaths.containsKey(node.id)) {
168 for (FinallyPathWrapper wrapper : dgraph.mapLongRangeFinallyPaths.get(node.id)) {
169 if (wrapper.source.equals(currentEntrypoint) && wrapper.destination.equals(nd.id)) {
177 Map<String, PrimitiveExpressionList> successorMap = mapData.computeIfAbsent(nd, k -> new HashMap<>());
178 LinkedList<String> nodeEntryPoints = new LinkedList<>(entryPoints);
180 if (setFinallyLongRangeEntryPaths.contains(node.id + "##" + nd.id)) {
181 nodeEntryPoints.addLast(node.id);
183 else if (!setFinallyShortRangeEntryPoints.contains(nd.id) && dgraph.mapLongRangeFinallyPaths.containsKey(node.id)) {
184 nodeEntryPoints.removeLast(); // currentEntrypoint should not be null at this point
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();
196 String nodeEntryKey = buildEntryPointKey(nodeEntryPoints);
197 if (!successorMap.containsKey(nodeEntryKey)) {
198 successorMap.put(nodeEntryKey, data.copy());
200 stackEntryPoint.add(nodeEntryPoints);
206 initStatementExprents(root);
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;
214 else if (entryPoints.size() == 1) {
215 return entryPoints.getFirst();
218 return String.join(":", entryPoints);
222 private static void collectCatchVars(Statement stat, FlattenStatementsHelper flatthelper, Map<String, VarExprent> map) {
223 List<VarExprent> lst = null;
225 if (stat.type == Statement.TYPE_CATCH_ALL) {
226 CatchAllStatement catchall = (CatchAllStatement)stat;
227 if (!catchall.isFinally()) {
228 lst = catchall.getVars();
231 else if (stat.type == Statement.TYPE_TRY_CATCH) {
232 lst = ((CatchStatement)stat).getVars();
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));
241 for (Statement st : stat.getStats()) {
242 collectCatchVars(st, flatthelper, map);
246 private static void initStatementExprents(Statement stat) {
249 for (Statement st : stat.getStats()) {
250 initStatementExprents(st);
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();
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;
267 switch (instr.opcode) {
268 case opc_aconst_null:
269 pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_NULL, null, offsets));
273 pushEx(stack, exprList, new ConstExprent(instr.operand(0), true, offsets));
277 pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_LONG, (long)(instr.opcode - opc_lconst_0), offsets));
282 pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_FLOAT, (float)(instr.opcode - opc_fconst_0), offsets));
286 pushEx(stack, exprList, new ConstExprent(VarType.VARTYPE_DOUBLE, (double)(instr.opcode - opc_dconst_0), offsets));
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));
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));
306 pushEx(stack, exprList, new VarExprent(instr.operand(0), varTypes[instr.opcode - opc_iload], varProcessor, offset));
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);
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));
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));
368 pushEx(stack, exprList, new FunctionExprent(func1[(instr.opcode - opc_iadd) / 4], stack, offsets));
382 pushEx(stack, exprList, new FunctionExprent(func2[(instr.opcode - opc_ishl) / 2], stack, offsets));
388 pushEx(stack, exprList, new FunctionExprent(FunctionExprent.FUNCTION_NEG, stack, offsets));
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));
412 pushEx(stack, exprList, new FunctionExprent(func3[instr.opcode - opc_i2l], stack, offsets));
419 pushEx(stack, exprList, new FunctionExprent(func4[instr.opcode - opc_lcmp], stack, offsets));
427 exprList.add(new IfExprent(negIfs[func5[instr.opcode - opc_ifeq]], stack, offsets));
437 exprList.add(new IfExprent(negIfs[func6[instr.opcode - opc_if_icmpeq]], stack, offsets));
441 exprList.add(new IfExprent(negIfs[func7[instr.opcode - opc_ifnull]], stack, offsets));
443 case opc_tableswitch:
444 case opc_lookupswitch:
445 exprList.add(new SwitchExprent(stack.pop(), offsets));
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,
459 case opc_monitorenter:
460 case opc_monitorexit:
461 exprList.add(new MonitorExprent(func8[instr.opcode - opc_monitorenter], stack.pop(), offsets));
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));
471 pushEx(stack, exprList,
472 new FieldExprent(pool.getLinkConstant(instr.operand(0)), instr.opcode == opc_getstatic ? null : stack.pop(), offsets));
476 Exprent valfield = stack.pop();
478 new FieldExprent(pool.getLinkConstant(instr.operand(0)), instr.opcode == opc_putstatic ? null : stack.pop(), offsets);
479 exprList.add(new AssignmentExprent(exprfield, valfield, offsets));
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));
489 List<PooledConstant> bootstrap_arguments = null;
490 if (instr.opcode == opc_invokedynamic && bootstrap != null) {
491 bootstrap_arguments = bootstrap.getMethodArguments(invoke_constant.index1);
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);
499 pushEx(stack, exprList, exprinv);
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);
511 pushEx(stack, exprList, new NewExprent(arrType, stack, dimensions, offsets));
514 pushEx(stack, exprList, new NewExprent(new VarType(arrTypeIds[instr.operand(0) - 4], 1), stack, 1, offsets));
517 pushEx(stack, exprList, stack.getByOffset(-1).copy());
520 insertByOffsetEx(-2, stack, exprList, -1);
523 if (stack.getByOffset(-2).getExprType().stackSize == 2) {
524 insertByOffsetEx(-2, stack, exprList, -1);
527 insertByOffsetEx(-3, stack, exprList, -1);
531 if (stack.getByOffset(-1).getExprType().stackSize == 2) {
532 pushEx(stack, exprList, stack.getByOffset(-1).copy());
535 pushEx(stack, exprList, stack.getByOffset(-2).copy());
536 pushEx(stack, exprList, stack.getByOffset(-2).copy());
540 if (stack.getByOffset(-1).getExprType().stackSize == 2) {
541 insertByOffsetEx(-2, stack, exprList, -1);
544 insertByOffsetEx(-3, stack, exprList, -2);
545 insertByOffsetEx(-3, stack, exprList, -1);
549 if (stack.getByOffset(-1).getExprType().stackSize == 2) {
550 if (stack.getByOffset(-2).getExprType().stackSize == 2) {
551 insertByOffsetEx(-2, stack, exprList, -1);
554 insertByOffsetEx(-3, stack, exprList, -1);
558 if (stack.getByOffset(-3).getExprType().stackSize == 2) {
559 insertByOffsetEx(-3, stack, exprList, -2);
560 insertByOffsetEx(-3, stack, exprList, -1);
563 insertByOffsetEx(-4, stack, exprList, -2);
564 insertByOffsetEx(-4, stack, exprList, -1);
569 insertByOffsetEx(-2, stack, exprList, -1);
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.
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) {
600 return block.getOriginalOffset(index);
603 List<BasicBlock> successors = block.getSuccessors();
604 if (successors.size() == 1) {
605 return successors.get(0).getOriginalOffset(0);
611 private void pushEx(ExpressionStack stack, List<Exprent> exprlist, Exprent exprent) {
612 pushEx(stack, exprlist, exprent, null);
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);
620 exprlist.add(new AssignmentExprent(var, exprent, null));
621 stack.push(var.copy());
624 private void insertByOffsetEx(int offset, ExpressionStack stack, List<Exprent> exprlist, int copyoffset) {
626 int base = VarExprent.STACK_BASE + stack.size();
628 LinkedList<VarExprent> lst = new LinkedList<>();
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());
638 Exprent exprent = lst.get(lst.size() + copyoffset).copy();
639 VarExprent var = new VarExprent(base + offset, exprent.getExprType(), varProcessor);
641 exprlist.add(new AssignmentExprent(var, exprent, null));
642 lst.add(0, (VarExprent)var.copy());
644 for (VarExprent expr : lst) {
649 public static String getTypeName(VarType type, List<TypeAnnotationWriteHelper> typePathWriteStack) {
650 return getTypeName(type, true, typePathWriteStack);
653 public static String getTypeName(VarType type, boolean getShort, List<TypeAnnotationWriteHelper> typePathWriteStack) {
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);
662 if (path != null && path.getTypePathKind() == StructTypePath.Kind.ARRAY.getOpcode() &&
663 typeAnnotationWriteHelper.getPaths().size() == type.arrayDim
665 typeAnnotationWriteHelper.writeTo(sb);
670 if (tp <= CodeConstants.TYPE_BOOLEAN) {
671 sb.append(typeNames[tp]);
672 return sb.toString();
674 else if (tp == CodeConstants.TYPE_UNKNOWN) {
675 sb.append(UNKNOWN_TYPE_STRING);
676 return sb.toString(); // INFO: should not occur
678 else if (tp == CodeConstants.TYPE_NULL) {
679 sb.append(NULL_TYPE_STRING);
680 return sb.toString(); // INFO: should not occur
682 else if (tp == CodeConstants.TYPE_VOID) {
684 return sb.toString();
686 else if (tp == CodeConstants.TYPE_OBJECT) {
689 ret = DecompilerContext.getImportCollector().getShortName(type.value);
691 ret = buildJavaClassName(type.value);
695 // FIXME: a warning should be logged
696 return UNDEFINED_TYPE_STRING;
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);
706 sb.append(nestedType);
707 if (i != nestedClasses.length - 1) sb.append(".");
710 return sb.toString();
713 throw new RuntimeException("invalid type");
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);
730 public static String getCastTypeName(VarType type, List<TypeAnnotationWriteHelper> typePathWriteStack) {
731 return getCastTypeName(type, true, typePathWriteStack);
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
744 arrayPaths.add(stack);
747 }).collect(Collectors.toList());
748 StringBuilder sb = new StringBuilder(getTypeName(type, getShort, notArrayPath));
749 writeArray(sb, type.arrayDim, arrayPaths);
750 return sb.toString();
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;
759 typePathWriteStack.removeIf(writeHelper -> {
760 if (it == writeHelper.getPaths().size()) {
761 if (ref.firstIteration) {
763 ref.firstIteration = false;
765 writeHelper.writeTo(sb);
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);
779 prlst.getExpressions().add(new AssignmentExprent(vartmp, var.copy(), null));
780 prlst.getStack().push(vartmp.copy());
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()));
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
803 public static TextBuffer jmpWrapper(Statement stat, int indent, boolean semicolon, BytecodeMappingTracer tracer) {
804 TextBuffer buf = stat.toJava(indent, tracer);
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);
812 switch (edge.getType()) {
813 case StatEdge.TYPE_BREAK:
814 addDeletedGotoInstructionMapping(stat, tracer);
817 case StatEdge.TYPE_CONTINUE:
818 addDeletedGotoInstructionMapping(stat, tracer);
819 buf.append("continue");
823 buf.append(" label").append(edge.closure.id.toString());
825 buf.append(";").appendLineSeparator();
826 tracer.incrementCurrentSourceLine();
830 if (buf.length() == 0 && semicolon) {
831 buf.appendIndent(indent).append(";").appendLineSeparator();
832 tracer.incrementCurrentSourceLine();
838 public static String buildJavaClassName(String name) {
839 String res = name.replace('/', '.');
841 if (res.contains("$")) { // attempt to invoke foreign member
843 StructClass cl = DecompilerContext.getStructContext().getClass(name);
844 if (cl == null || !cl.isOwn()) {
845 res = res.replace('$', '.');
852 public static TextBuffer listToJava(List<? extends Exprent> lst, int indent, BytecodeMappingTracer tracer) {
853 if (lst == null || lst.isEmpty()) {
854 return new TextBuffer();
857 TextBuffer buf = new TextBuffer();
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();
866 TextBuffer content = expr.toJava(indent, tracer);
868 if (content.length() > 0) {
869 if (expr.type != Exprent.EXPRENT_VAR || !((VarExprent)expr).isClassDef()) {
870 buf.appendIndent(indent);
873 if (expr.type == Exprent.EXPRENT_MONITOR && ((MonitorExprent)expr).getMonType() == MonitorExprent.MONITOR_ENTER) {
874 buf.append("{}"); // empty synchronized block
876 if (endsWithSemicolon(expr)) {
879 buf.appendLineSeparator();
880 tracer.incrementCurrentSourceLine();
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);
892 else if (arrType.type == CodeConstants.TYPE_FLOAT) {
893 defaultVal = new ConstExprent(VarType.VARTYPE_FLOAT, 0f, null);
895 else if (arrType.type == CodeConstants.TYPE_LONG) {
896 defaultVal = new ConstExprent(VarType.VARTYPE_LONG, 0L, null);
898 else if (arrType.type == CodeConstants.TYPE_DOUBLE) {
899 defaultVal = new ConstExprent(VarType.VARTYPE_DOUBLE, 0d, null);
901 else { // integer types
902 defaultVal = new ConstExprent(0, true, null);
907 public static boolean getCastedExprent(Exprent exprent,
912 BytecodeMappingTracer tracer) {
913 return getCastedExprent(exprent, leftType, buffer, indent, castNull, false, false, false, tracer);
916 public static boolean getCastedExprent(Exprent exprent,
922 boolean castNarrowing,
924 BytecodeMappingTracer tracer) {
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);
938 VarType rightType = exprent.getExprType();
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));
946 boolean quote = cast && exprent.getPrecedence() >= FunctionExprent.getPrecedence(FunctionExprent.FUNCTION_CAST);
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;
953 else if (leftType.equals(VarType.VARTYPE_SHORT_OBJ)) {
954 leftType = VarType.VARTYPE_SHORT;
958 if (cast) buffer.append('(').append(ExprProcessor.getCastTypeName(leftType, Collections.emptyList())).append(')');
960 if (quote) buffer.append('(');
962 if (exprent.type == Exprent.EXPRENT_CONST) {
963 ((ConstExprent) exprent).adjustConstType(leftType);
966 buffer.append(exprent.toJava(indent, tracer));
968 if (quote) buffer.append(')');
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:
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);