1 // Copyright 2000-2021 JetBrains s.r.o. and contributors. 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;
4 import org.jetbrains.java.decompiler.code.cfg.BasicBlock;
5 import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph;
6 import org.jetbrains.java.decompiler.code.cfg.ExceptionRangeCFG;
7 import org.jetbrains.java.decompiler.main.DecompilerContext;
8 import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
9 import org.jetbrains.java.decompiler.modules.decompiler.decompose.FastExtendedPostdominanceHelper;
10 import org.jetbrains.java.decompiler.modules.decompiler.deobfuscator.IrreducibleCFGDeobfuscator;
11 import org.jetbrains.java.decompiler.modules.decompiler.stats.*;
12 import org.jetbrains.java.decompiler.util.FastFixedSetFactory;
13 import org.jetbrains.java.decompiler.util.FastFixedSetFactory.FastFixedSet;
14 import org.jetbrains.java.decompiler.util.VBStyleCollection;
18 public final class DomHelper {
21 private static RootStatement graphToStatement(ControlFlowGraph graph) {
23 VBStyleCollection<Statement, Integer> stats = new VBStyleCollection<>();
24 VBStyleCollection<BasicBlock, Integer> blocks = graph.getBlocks();
26 for (BasicBlock block : blocks) {
27 stats.addWithKey(new BasicBlockStatement(block), block.id);
30 BasicBlock firstblock = graph.getFirst();
32 Statement firstst = stats.getWithKey(firstblock.id);
33 // dummy exit statement
34 DummyExitStatement dummyexit = new DummyExitStatement();
37 if (stats.size() > 1 || firstblock.isSuccessor(firstblock)) { // multiple basic blocks or an infinite loop of one block
38 general = new GeneralStatement(firstst, stats, null);
40 else { // one straightforward basic block
41 RootStatement root = new RootStatement(firstst, dummyexit);
42 firstst.addSuccessor(new StatEdge(StatEdge.TYPE_BREAK, firstst, dummyexit, root));
47 for (BasicBlock block : blocks) {
48 Statement stat = stats.getWithKey(block.id);
50 for (BasicBlock succ : block.getSuccessors()) {
51 Statement stsucc = stats.getWithKey(succ.id);
54 if (stsucc == firstst) {
55 type = StatEdge.TYPE_CONTINUE;
57 else if (graph.getFinallyExits().contains(block)) {
58 type = StatEdge.TYPE_FINALLYEXIT;
61 else if (succ.id == graph.getLast().id) {
62 type = StatEdge.TYPE_BREAK;
66 type = StatEdge.TYPE_REGULAR;
69 stat.addSuccessor(new StatEdge(type, stat, (type == StatEdge.TYPE_CONTINUE) ? general : stsucc,
70 (type == StatEdge.TYPE_REGULAR) ? null : general));
74 for (BasicBlock succex : block.getSuccessorExceptions()) {
75 Statement stsuccex = stats.getWithKey(succex.id);
77 ExceptionRangeCFG range = graph.getExceptionRange(succex, block);
78 if (!range.isCircular()) {
79 stat.addSuccessor(new StatEdge(stat, stsuccex, range.getExceptionTypes()));
84 general.buildContinueSet();
85 general.buildMonitorFlags();
86 return new RootStatement(general, dummyexit);
89 public static VBStyleCollection<List<Integer>, Integer> calcPostDominators(Statement container) {
91 HashMap<Statement, FastFixedSet<Statement>> lists = new HashMap<>();
93 StrongConnectivityHelper schelper = new StrongConnectivityHelper(container);
94 List<List<Statement>> components = schelper.getComponents();
96 List<Statement> lstStats = container.getPostReversePostOrderList(StrongConnectivityHelper.getExitReps(components));
98 FastFixedSetFactory<Statement> factory = new FastFixedSetFactory<>(lstStats);
100 FastFixedSet<Statement> setFlagNodes = factory.spawnEmptySet();
101 setFlagNodes.setAllElements();
103 FastFixedSet<Statement> initSet = factory.spawnEmptySet();
104 initSet.setAllElements();
106 for (List<Statement> lst : components) {
107 FastFixedSet<Statement> tmpSet;
109 if (StrongConnectivityHelper.isExitComponent(lst)) {
110 tmpSet = factory.spawnEmptySet();
114 tmpSet = initSet.getCopy();
117 for (Statement stat : lst) {
118 lists.put(stat, tmpSet);
124 for (Statement stat : lstStats) {
126 if (!setFlagNodes.contains(stat)) {
129 setFlagNodes.remove(stat);
131 FastFixedSet<Statement> doms = lists.get(stat);
132 FastFixedSet<Statement> domsSuccs = factory.spawnEmptySet();
134 List<Statement> lstSuccs = stat.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_FORWARD);
135 for (int j = 0; j < lstSuccs.size(); j++) {
136 Statement succ = lstSuccs.get(j);
137 FastFixedSet<Statement> succlst = lists.get(succ);
140 domsSuccs.union(succlst);
143 domsSuccs.intersection(succlst);
147 if (!domsSuccs.contains(stat)) {
151 if (!Objects.equals(domsSuccs, doms)) {
153 lists.put(stat, domsSuccs);
155 List<Statement> lstPreds = stat.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD);
156 for (Statement pred : lstPreds) {
157 setFlagNodes.add(pred);
162 while (!setFlagNodes.isEmpty());
164 VBStyleCollection<List<Integer>, Integer> ret = new VBStyleCollection<>();
165 List<Statement> lstRevPost = container.getReversePostOrderList(); // sort order crucial!
167 final HashMap<Integer, Integer> mapSortOrder = new HashMap<>();
168 for (int i = 0; i < lstRevPost.size(); i++) {
169 mapSortOrder.put(lstRevPost.get(i).id, i);
172 for (Statement st : lstStats) {
174 List<Integer> lstPosts = new ArrayList<>();
175 for (Statement stt : lists.get(st)) {
176 lstPosts.add(stt.id);
179 lstPosts.sort(Comparator.comparing(mapSortOrder::get));
181 if (lstPosts.size() > 1 && lstPosts.get(0).intValue() == st.id) {
182 lstPosts.add(lstPosts.remove(0));
185 ret.addWithKey(lstPosts, st.id);
191 public static RootStatement parseGraph(ControlFlowGraph graph) {
193 RootStatement root = graphToStatement(graph);
195 if (!processStatement(root, new HashMap<>())) {
198 // DotExporter.toDotFile(root.getFirst().getStats().get(13), new File("c:\\Temp\\stat1.dot"));
199 // } catch (Exception ex) {
200 // ex.printStackTrace();
202 throw new RuntimeException("parsing failure!");
205 LabelHelper.lowContinueLabels(root, new HashSet<>());
207 SequenceHelper.condenseSequences(root);
208 root.buildMonitorFlags();
210 // build synchronized statements
211 buildSynchronized(root);
216 public static void removeSynchronizedHandler(Statement stat) {
218 for (Statement st : stat.getStats()) {
219 removeSynchronizedHandler(st);
222 if (stat.type == Statement.TYPE_SYNCHRONIZED) {
223 ((SynchronizedStatement)stat).removeExc();
228 private static void buildSynchronized(Statement stat) {
230 for (Statement st : stat.getStats()) {
231 buildSynchronized(st);
234 if (stat.type == Statement.TYPE_SEQUENCE) {
238 boolean found = false;
240 List<Statement> lst = stat.getStats();
241 for (int i = 0; i < lst.size() - 1; i++) {
242 Statement current = lst.get(i); // basic block
244 if (current.isMonitorEnter()) {
246 Statement next = lst.get(i + 1);
247 Statement nextDirect = next;
249 while (next.type == Statement.TYPE_SEQUENCE) {
250 next = next.getFirst();
253 if (next.type == Statement.TYPE_CATCH_ALL) {
255 CatchAllStatement ca = (CatchAllStatement)next;
257 if (ca.getFirst().isContainsMonitorExit() && ca.getHandler().isContainsMonitorExit()) {
259 // remove the head block from sequence
260 current.removeSuccessor(current.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL).get(0));
262 for (StatEdge edge : current.getPredecessorEdges(Statement.STATEDGE_DIRECT_ALL)) {
263 current.removePredecessor(edge);
264 edge.getSource().changeEdgeNode(Statement.DIRECTION_FORWARD, edge, nextDirect);
265 nextDirect.addPredecessor(edge);
268 stat.getStats().removeWithKey(current.id);
269 stat.setFirst(stat.getStats().get(0));
272 SynchronizedStatement sync = new SynchronizedStatement(current, ca.getFirst(), ca.getHandler());
275 for (StatEdge edge : new HashSet<>(ca.getLabelEdges())) {
276 sync.addLabeledEdge(edge);
279 current.addSuccessor(new StatEdge(StatEdge.TYPE_REGULAR, current, ca.getFirst()));
281 ca.getParent().replaceStatement(ca, sync);
296 private static boolean processStatement(Statement general, HashMap<Integer, Set<Integer>> mapExtPost) {
298 if (general.type == Statement.TYPE_ROOT) {
299 Statement stat = general.getFirst();
300 if (stat.type != Statement.TYPE_GENERAL) {
304 boolean complete = processStatement(stat, mapExtPost);
306 // replace general purpose statement with simple one
307 general.replaceStatement(stat, stat.getFirst());
313 boolean mapRefreshed = mapExtPost.isEmpty();
315 for (int mapstage = 0; mapstage < 2; mapstage++) {
317 for (int reducibility = 0;
319 reducibility++) { // FIXME: implement proper node splitting. For now up to 5 nodes in sequence are splitted.
321 if (reducibility > 0) {
324 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
325 // } catch(Exception ex) {ex.printStackTrace();}
327 // take care of irreducible control flow graphs
328 if (IrreducibleCFGDeobfuscator.isStatementIrreducible(general)) {
329 if (!IrreducibleCFGDeobfuscator.splitIrreducibleNode(general)) {
330 DecompilerContext.getLogger().writeMessage("Irreducible statement cannot be decomposed!", IFernflowerLogger.Severity.ERROR);
335 if (mapstage == 2 || mapRefreshed) { // last chance lost
336 DecompilerContext.getLogger().writeMessage("Statement cannot be decomposed although reducible!", IFernflowerLogger.Severity.ERROR);
342 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
343 // } catch(Exception ex) {ex.printStackTrace();}
345 mapExtPost = new HashMap<>();
349 for (int i = 0; i < 2; i++) {
351 boolean forceall = i != 0;
355 if (findSimpleStatements(general, mapExtPost)) {
359 if (general.type == Statement.TYPE_PLACEHOLDER) {
363 Statement stat = findGeneralStatement(general, forceall, mapExtPost);
366 boolean complete = processStatement(stat, general.getFirst() == stat ? mapExtPost : new HashMap<>());
369 // replace general purpose statement with simple one
370 general.replaceStatement(stat, stat.getFirst());
376 mapExtPost = new HashMap<>();
387 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
388 // } catch (Exception ex) {
389 // ex.printStackTrace();
397 mapExtPost = new HashMap<>();
404 private static Statement findGeneralStatement(Statement stat, boolean forceall, HashMap<Integer, Set<Integer>> mapExtPost) {
406 VBStyleCollection<Statement, Integer> stats = stat.getStats();
407 VBStyleCollection<List<Integer>, Integer> vbPost;
409 if (mapExtPost.isEmpty()) {
410 FastExtendedPostdominanceHelper extpost = new FastExtendedPostdominanceHelper();
411 mapExtPost.putAll(extpost.getExtendedPostdominators(stat));
415 vbPost = new VBStyleCollection<>();
416 List<Statement> lstAll = stat.getPostReversePostOrderList();
418 for (Statement st : lstAll) {
419 Set<Integer> set = mapExtPost.get(st.id);
421 vbPost.addWithKey(new ArrayList<>(set), st.id); // FIXME: sort order!!
426 Set<Integer> setFirst = mapExtPost.get(stat.getFirst().id);
427 if (setFirst != null) {
428 for (Integer id : setFirst) {
429 List<Integer> lst = vbPost.getWithKey(id);
431 vbPost.addWithKey(lst = new ArrayList<>(), id);
438 vbPost = calcPostDominators(stat);
441 for (int k = 0; k < vbPost.size(); k++) {
443 Integer headid = vbPost.getKey(k);
444 List<Integer> posts = vbPost.get(k);
446 if (!mapExtPost.containsKey(headid) &&
447 !(posts.size() == 1 && posts.get(0).equals(headid))) {
451 Statement head = stats.getWithKey(headid);
453 Set<Integer> setExtPosts = mapExtPost.get(headid);
455 for (Integer postId : posts) {
456 if (!postId.equals(headid) && !setExtPosts.contains(postId)) {
460 Statement post = stats.getWithKey(postId);
462 if (post == null) { // possible in case of an inherited postdominance set
466 boolean same = (post == head);
468 HashSet<Statement> setNodes = new HashSet<>();
469 HashSet<Statement> setPreds = new HashSet<>();
471 // collect statement nodes
472 HashSet<Statement> setHandlers = new HashSet<>();
473 setHandlers.add(head);
476 boolean hdfound = false;
477 for (Statement handler : setHandlers) {
478 if (setNodes.contains(handler)) {
482 boolean addhd = (setNodes.size() == 0); // first handler == head
484 List<Statement> hdsupp = handler.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_BACKWARD);
485 addhd = (setNodes.containsAll(hdsupp) && (setNodes.size() > hdsupp.size()
486 || setNodes.size() == 1)); // strict subset
490 LinkedList<Statement> lstStack = new LinkedList<>();
491 lstStack.add(handler);
493 while (!lstStack.isEmpty()) {
494 Statement st = lstStack.remove(0);
496 if (!(setNodes.contains(st) || (!same && st == post))) {
499 // record predeccessors except for the head
500 setPreds.addAll(st.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD));
503 // put successors on the stack
504 lstStack.addAll(st.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_FORWARD));
507 setHandlers.addAll(st.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_FORWARD));
512 setHandlers.remove(handler);
522 // check exception handlers
524 for (Statement st : setNodes) {
525 setHandlers.addAll(st.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_FORWARD));
527 setHandlers.removeAll(setNodes);
529 boolean excok = true;
530 for (Statement handler : setHandlers) {
531 if (!handler.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_BACKWARD).containsAll(setNodes)) {
537 // build statement and return
541 setPreds.removeAll(setNodes);
542 if (setPreds.size() == 0) {
543 if ((setNodes.size() > 1 ||
544 head.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD).contains(head))
545 && setNodes.size() < stats.size()) {
546 if (checkSynchronizedCompleteness(setNodes)) {
547 res = new GeneralStatement(head, setNodes, same ? null : post);
548 stat.collapseNodesToStatement(res);
561 private static boolean checkSynchronizedCompleteness(Set<Statement> setNodes) {
563 for (Statement stat : setNodes) {
564 if (stat.isMonitorEnter()) {
565 List<StatEdge> lstSuccs = stat.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL);
566 if (lstSuccs.size() != 1 || lstSuccs.get(0).getType() != StatEdge.TYPE_REGULAR) {
570 if (!setNodes.contains(lstSuccs.get(0).getDestination())) {
579 private static boolean findSimpleStatements(Statement stat, HashMap<Integer, Set<Integer>> mapExtPost) {
581 boolean found, success = false;
586 List<Statement> lstStats = stat.getPostReversePostOrderList();
587 for (Statement st : lstStats) {
589 Statement result = detectStatement(st);
591 if (result != null) {
593 if (stat.type == Statement.TYPE_GENERAL && result.getFirst() == stat.getFirst() &&
594 stat.getStats().size() == result.getStats().size()) {
595 // mark general statement
596 stat.type = Statement.TYPE_PLACEHOLDER;
599 stat.collapseNodesToStatement(result);
601 // update the postdominator map
602 if (!mapExtPost.isEmpty()) {
603 HashSet<Integer> setOldNodes = new HashSet<>();
604 for (Statement old : result.getStats()) {
605 setOldNodes.add(old.id);
608 Integer newid = result.id;
610 for (Integer key : new ArrayList<>(mapExtPost.keySet())) {
611 Set<Integer> set = mapExtPost.get(key);
613 int oldsize = set.size();
614 set.removeAll(setOldNodes);
616 if (setOldNodes.contains(key)) {
617 mapExtPost.computeIfAbsent(newid, k -> new HashSet<>()).addAll(set);
618 mapExtPost.remove(key);
621 if (set.size() < oldsize) {
644 private static Statement detectStatement(Statement head) {
648 if ((res = DoStatement.isHead(head)) != null) {
652 if ((res = SwitchStatement.isHead(head)) != null) {
656 if ((res = IfStatement.isHead(head)) != null) {
660 // synchronized statements will be identified later
661 // right now they are recognized as catchall
663 if ((res = SequenceStatement.isHead2Block(head)) != null) {
667 if ((res = CatchStatement.isHead(head)) != null) {
671 if ((res = CatchAllStatement.isHead(head)) != null) {