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 connectivityHelper = new StrongConnectivityHelper(container);
95 List<Statement> lstStats = container.getPostReversePostOrderList(connectivityHelper.getExitReps());
97 FastFixedSetFactory<Statement> factory = new FastFixedSetFactory<>(lstStats);
99 FastFixedSet<Statement> setFlagNodes = factory.spawnEmptySet();
100 setFlagNodes.setAllElements();
102 FastFixedSet<Statement> initSet = factory.spawnEmptySet();
103 initSet.setAllElements();
105 for (List<Statement> lst : connectivityHelper.getComponents()) {
106 FastFixedSet<Statement> tmpSet;
108 if (StrongConnectivityHelper.isExitComponent(lst)) {
109 tmpSet = factory.spawnEmptySet();
113 tmpSet = initSet.getCopy();
116 for (Statement stat : lst) {
117 lists.put(stat, tmpSet);
123 for (Statement stat : lstStats) {
125 if (!setFlagNodes.contains(stat)) {
128 setFlagNodes.remove(stat);
130 FastFixedSet<Statement> doms = lists.get(stat);
131 FastFixedSet<Statement> domsSuccs = factory.spawnEmptySet();
133 List<Statement> lstSuccs = stat.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_FORWARD);
134 for (int j = 0; j < lstSuccs.size(); j++) {
135 Statement succ = lstSuccs.get(j);
136 FastFixedSet<Statement> succlst = lists.get(succ);
139 domsSuccs.union(succlst);
142 domsSuccs.intersection(succlst);
146 if (!domsSuccs.contains(stat)) {
150 if (!Objects.equals(domsSuccs, doms)) {
152 lists.put(stat, domsSuccs);
154 List<Statement> lstPreds = stat.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD);
155 for (Statement pred : lstPreds) {
156 setFlagNodes.add(pred);
161 while (!setFlagNodes.isEmpty());
163 VBStyleCollection<List<Integer>, Integer> ret = new VBStyleCollection<>();
164 List<Statement> lstRevPost = container.getReversePostOrderList(); // sort order crucial!
166 final HashMap<Integer, Integer> mapSortOrder = new HashMap<>();
167 for (int i = 0; i < lstRevPost.size(); i++) {
168 mapSortOrder.put(lstRevPost.get(i).id, i);
171 for (Statement st : lstStats) {
173 List<Integer> lstPosts = new ArrayList<>();
174 for (Statement stt : lists.get(st)) {
175 lstPosts.add(stt.id);
178 lstPosts.sort(Comparator.comparing(mapSortOrder::get));
180 if (lstPosts.size() > 1 && lstPosts.get(0).intValue() == st.id) {
181 lstPosts.add(lstPosts.remove(0));
184 ret.addWithKey(lstPosts, st.id);
190 public static RootStatement parseGraph(ControlFlowGraph graph) {
192 RootStatement root = graphToStatement(graph);
194 if (!processStatement(root, new HashMap<>())) {
197 // DotExporter.toDotFile(root.getFirst().getStats().get(13), new File("c:\\Temp\\stat1.dot"));
198 // } catch (Exception ex) {
199 // ex.printStackTrace();
201 throw new RuntimeException("parsing failure!");
204 LabelHelper.lowContinueLabels(root, new HashSet<>());
206 SequenceHelper.condenseSequences(root);
207 root.buildMonitorFlags();
209 // build synchronized statements
210 buildSynchronized(root);
215 public static void removeSynchronizedHandler(Statement stat) {
217 for (Statement st : stat.getStats()) {
218 removeSynchronizedHandler(st);
221 if (stat.type == Statement.TYPE_SYNCHRONIZED) {
222 ((SynchronizedStatement)stat).removeExc();
227 private static void buildSynchronized(Statement stat) {
229 for (Statement st : stat.getStats()) {
230 buildSynchronized(st);
233 if (stat.type == Statement.TYPE_SEQUENCE) {
237 boolean found = false;
239 List<Statement> lst = stat.getStats();
240 for (int i = 0; i < lst.size() - 1; i++) {
241 Statement current = lst.get(i); // basic block
243 if (current.isMonitorEnter()) {
245 Statement next = lst.get(i + 1);
246 Statement nextDirect = next;
248 while (next.type == Statement.TYPE_SEQUENCE) {
249 next = next.getFirst();
252 if (next.type == Statement.TYPE_CATCH_ALL) {
254 CatchAllStatement ca = (CatchAllStatement)next;
256 if (ca.getFirst().isContainsMonitorExit() && ca.getHandler().isContainsMonitorExit()) {
258 // remove the head block from sequence
259 current.removeSuccessor(current.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL).get(0));
261 for (StatEdge edge : current.getPredecessorEdges(Statement.STATEDGE_DIRECT_ALL)) {
262 current.removePredecessor(edge);
263 edge.getSource().changeEdgeNode(Statement.DIRECTION_FORWARD, edge, nextDirect);
264 nextDirect.addPredecessor(edge);
267 stat.getStats().removeWithKey(current.id);
268 stat.setFirst(stat.getStats().get(0));
271 SynchronizedStatement sync = new SynchronizedStatement(current, ca.getFirst(), ca.getHandler());
274 for (StatEdge edge : new HashSet<>(ca.getLabelEdges())) {
275 sync.addLabeledEdge(edge);
278 current.addSuccessor(new StatEdge(StatEdge.TYPE_REGULAR, current, ca.getFirst()));
280 ca.getParent().replaceStatement(ca, sync);
295 private static boolean processStatement(Statement general, HashMap<Integer, Set<Integer>> mapExtPost) {
297 if (general.type == Statement.TYPE_ROOT) {
298 Statement stat = general.getFirst();
299 if (stat.type != Statement.TYPE_GENERAL) {
303 boolean complete = processStatement(stat, mapExtPost);
305 // replace general purpose statement with simple one
306 general.replaceStatement(stat, stat.getFirst());
312 boolean mapRefreshed = mapExtPost.isEmpty();
314 for (int mapstage = 0; mapstage < 2; mapstage++) {
316 for (int reducibility = 0;
318 reducibility++) { // FIXME: implement proper node splitting. For now up to 5 nodes in sequence are splitted.
320 if (reducibility > 0) {
323 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
324 // } catch(Exception ex) {ex.printStackTrace();}
326 // take care of irreducible control flow graphs
327 if (IrreducibleCFGDeobfuscator.isStatementIrreducible(general)) {
328 if (!IrreducibleCFGDeobfuscator.splitIrreducibleNode(general)) {
329 DecompilerContext.getLogger().writeMessage("Irreducible statement cannot be decomposed!", IFernflowerLogger.Severity.ERROR);
334 if (mapstage == 2 || mapRefreshed) { // last chance lost
335 DecompilerContext.getLogger().writeMessage("Statement cannot be decomposed although reducible!", IFernflowerLogger.Severity.ERROR);
341 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
342 // } catch(Exception ex) {ex.printStackTrace();}
344 mapExtPost = new HashMap<>();
348 for (int i = 0; i < 2; i++) {
350 boolean forceall = i != 0;
354 if (findSimpleStatements(general, mapExtPost)) {
358 if (general.type == Statement.TYPE_PLACEHOLDER) {
362 Statement stat = findGeneralStatement(general, forceall, mapExtPost);
365 boolean complete = processStatement(stat, general.getFirst() == stat ? mapExtPost : new HashMap<>());
368 // replace general purpose statement with simple one
369 general.replaceStatement(stat, stat.getFirst());
375 mapExtPost = new HashMap<>();
386 // DotExporter.toDotFile(general, new File("c:\\Temp\\stat1.dot"));
387 // } catch (Exception ex) {
388 // ex.printStackTrace();
396 mapExtPost = new HashMap<>();
403 private static Statement findGeneralStatement(Statement stat, boolean forceall, HashMap<Integer, Set<Integer>> mapExtPost) {
405 VBStyleCollection<Statement, Integer> stats = stat.getStats();
406 VBStyleCollection<List<Integer>, Integer> vbPost;
408 if (mapExtPost.isEmpty()) {
409 FastExtendedPostdominanceHelper extpost = new FastExtendedPostdominanceHelper();
410 mapExtPost.putAll(extpost.getExtendedPostdominators(stat));
414 vbPost = new VBStyleCollection<>();
415 List<Statement> lstAll = stat.getPostReversePostOrderList();
417 for (Statement st : lstAll) {
418 Set<Integer> set = mapExtPost.get(st.id);
420 vbPost.addWithKey(new ArrayList<>(set), st.id); // FIXME: sort order!!
425 Set<Integer> setFirst = mapExtPost.get(stat.getFirst().id);
426 if (setFirst != null) {
427 for (Integer id : setFirst) {
428 List<Integer> lst = vbPost.getWithKey(id);
430 vbPost.addWithKey(lst = new ArrayList<>(), id);
437 vbPost = calcPostDominators(stat);
440 for (int k = 0; k < vbPost.size(); k++) {
442 Integer headid = vbPost.getKey(k);
443 List<Integer> posts = vbPost.get(k);
445 if (!mapExtPost.containsKey(headid) &&
446 !(posts.size() == 1 && posts.get(0).equals(headid))) {
450 Statement head = stats.getWithKey(headid);
452 Set<Integer> setExtPosts = mapExtPost.get(headid);
454 for (Integer postId : posts) {
455 if (!postId.equals(headid) && !setExtPosts.contains(postId)) {
459 Statement post = stats.getWithKey(postId);
461 if (post == null) { // possible in case of an inherited postdominance set
465 boolean same = (post == head);
467 HashSet<Statement> setNodes = new HashSet<>();
468 HashSet<Statement> setPreds = new HashSet<>();
470 // collect statement nodes
471 HashSet<Statement> setHandlers = new HashSet<>();
472 setHandlers.add(head);
475 boolean hdfound = false;
476 for (Statement handler : setHandlers) {
477 if (setNodes.contains(handler)) {
481 boolean addhd = (setNodes.size() == 0); // first handler == head
483 List<Statement> hdsupp = handler.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_BACKWARD);
484 addhd = (setNodes.containsAll(hdsupp) && (setNodes.size() > hdsupp.size()
485 || setNodes.size() == 1)); // strict subset
489 LinkedList<Statement> lstStack = new LinkedList<>();
490 lstStack.add(handler);
492 while (!lstStack.isEmpty()) {
493 Statement st = lstStack.remove(0);
495 if (!(setNodes.contains(st) || (!same && st == post))) {
498 // record predeccessors except for the head
499 setPreds.addAll(st.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD));
502 // put successors on the stack
503 lstStack.addAll(st.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_FORWARD));
506 setHandlers.addAll(st.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_FORWARD));
511 setHandlers.remove(handler);
521 // check exception handlers
523 for (Statement st : setNodes) {
524 setHandlers.addAll(st.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_FORWARD));
526 setHandlers.removeAll(setNodes);
528 boolean excok = true;
529 for (Statement handler : setHandlers) {
530 if (!handler.getNeighbours(StatEdge.TYPE_EXCEPTION, Statement.DIRECTION_BACKWARD).containsAll(setNodes)) {
536 // build statement and return
540 setPreds.removeAll(setNodes);
541 if (setPreds.size() == 0) {
542 if ((setNodes.size() > 1 ||
543 head.getNeighbours(StatEdge.TYPE_REGULAR, Statement.DIRECTION_BACKWARD).contains(head))
544 && setNodes.size() < stats.size()) {
545 if (checkSynchronizedCompleteness(setNodes)) {
546 res = new GeneralStatement(head, setNodes, same ? null : post);
547 stat.collapseNodesToStatement(res);
560 private static boolean checkSynchronizedCompleteness(Set<Statement> setNodes) {
562 for (Statement stat : setNodes) {
563 if (stat.isMonitorEnter()) {
564 List<StatEdge> lstSuccs = stat.getSuccessorEdges(Statement.STATEDGE_DIRECT_ALL);
565 if (lstSuccs.size() != 1 || lstSuccs.get(0).getType() != StatEdge.TYPE_REGULAR) {
569 if (!setNodes.contains(lstSuccs.get(0).getDestination())) {
578 private static boolean findSimpleStatements(Statement stat, HashMap<Integer, Set<Integer>> mapExtPost) {
580 boolean found, success = false;
585 List<Statement> lstStats = stat.getPostReversePostOrderList();
586 for (Statement st : lstStats) {
588 Statement result = detectStatement(st);
590 if (result != null) {
592 if (stat.type == Statement.TYPE_GENERAL && result.getFirst() == stat.getFirst() &&
593 stat.getStats().size() == result.getStats().size()) {
594 // mark general statement
595 stat.type = Statement.TYPE_PLACEHOLDER;
598 stat.collapseNodesToStatement(result);
600 // update the postdominator map
601 if (!mapExtPost.isEmpty()) {
602 HashSet<Integer> setOldNodes = new HashSet<>();
603 for (Statement old : result.getStats()) {
604 setOldNodes.add(old.id);
607 Integer newid = result.id;
609 for (Integer key : new ArrayList<>(mapExtPost.keySet())) {
610 Set<Integer> set = mapExtPost.get(key);
612 int oldsize = set.size();
613 set.removeAll(setOldNodes);
615 if (setOldNodes.contains(key)) {
616 mapExtPost.computeIfAbsent(newid, k -> new HashSet<>()).addAll(set);
617 mapExtPost.remove(key);
620 if (set.size() < oldsize) {
643 private static Statement detectStatement(Statement head) {
647 if ((res = DoStatement.isHead(head)) != null) {
651 if ((res = SwitchStatement.isHead(head)) != null) {
655 if ((res = IfStatement.isHead(head)) != null) {
659 // synchronized statements will be identified later
660 // right now they are recognized as catchall
662 if ((res = SequenceStatement.isHead2Block(head)) != null) {
666 if ((res = CatchStatement.isHead(head)) != null) {
670 if ((res = CatchAllStatement.isHead(head)) != null) {