|
18 | 18 | */ |
19 | 19 | package au.com.integradev.delphi.checks; |
20 | 20 |
|
21 | | -import au.com.integradev.delphi.antlr.ast.node.RoutineImplementationNodeImpl; |
22 | | -import au.com.integradev.delphi.cfg.ControlFlowGraphFactory; |
23 | 21 | import au.com.integradev.delphi.cfg.api.Block; |
24 | 22 | import au.com.integradev.delphi.cfg.api.ControlFlowGraph; |
25 | 23 | import au.com.integradev.delphi.cfg.api.Finally; |
| 24 | +import au.com.integradev.delphi.cfg.api.Terminated; |
26 | 25 | import au.com.integradev.delphi.cfg.api.UnconditionalJump; |
| 26 | +import au.com.integradev.delphi.utils.ControlFlowGraphUtils; |
| 27 | +import java.util.ArrayDeque; |
| 28 | +import java.util.Deque; |
27 | 29 | import org.sonar.check.Rule; |
28 | | -import org.sonar.plugins.communitydelphi.api.ast.AnonymousMethodNode; |
29 | 30 | import org.sonar.plugins.communitydelphi.api.ast.ArgumentListNode; |
30 | 31 | import org.sonar.plugins.communitydelphi.api.ast.DelphiNode; |
| 32 | +import org.sonar.plugins.communitydelphi.api.ast.FinallyBlockNode; |
31 | 33 | import org.sonar.plugins.communitydelphi.api.ast.NameReferenceNode; |
32 | | -import org.sonar.plugins.communitydelphi.api.ast.RoutineImplementationNode; |
| 34 | +import org.sonar.plugins.communitydelphi.api.ast.TryStatementNode; |
33 | 35 | import org.sonar.plugins.communitydelphi.api.check.DelphiCheck; |
34 | 36 | import org.sonar.plugins.communitydelphi.api.check.DelphiCheckContext; |
35 | 37 | import org.sonar.plugins.communitydelphi.api.symbol.declaration.NameDeclaration; |
|
39 | 41 | public class RedundantJumpCheck extends DelphiCheck { |
40 | 42 | private static final String MESSAGE = "Remove this redundant jump."; |
41 | 43 |
|
| 44 | + private final Deque<TryStatementNode> tryFinallyStatements = new ArrayDeque<>(); |
| 45 | + |
42 | 46 | @Override |
43 | | - public DelphiCheckContext visit(RoutineImplementationNode routine, DelphiCheckContext context) { |
44 | | - ControlFlowGraph cfg = ((RoutineImplementationNodeImpl) routine).getControlFlowGraph(); |
45 | | - if (cfg != null) { |
46 | | - cfg.getBlocks().forEach(block -> checkBlock(block, context)); |
| 47 | + public DelphiCheckContext visit(TryStatementNode node, DelphiCheckContext data) { |
| 48 | + boolean finallyBlock = node.hasFinallyBlock(); |
| 49 | + if (!finallyBlock) { |
| 50 | + return super.visit(node, data); |
47 | 51 | } |
48 | | - |
49 | | - return super.visit(routine, context); |
| 52 | + this.tryFinallyStatements.push(node); |
| 53 | + super.visit(node.getStatementList(), data); |
| 54 | + this.tryFinallyStatements.pop(); |
| 55 | + return super.visit(node.getFinallyBlock(), data); |
50 | 56 | } |
51 | 57 |
|
52 | 58 | @Override |
53 | | - public DelphiCheckContext visit(AnonymousMethodNode routine, DelphiCheckContext context) { |
54 | | - ControlFlowGraph cfg = ControlFlowGraphFactory.create(routine.getStatementBlock()); |
55 | | - cfg.getBlocks().forEach(block -> checkBlock(block, context)); |
| 59 | + public DelphiCheckContext visit(NameReferenceNode node, DelphiCheckContext data) { |
| 60 | + RoutineNameDeclaration routineNameDeclaration = getRoutineNameDeclaration(node); |
| 61 | + if (routineNameDeclaration != null |
| 62 | + && (isContinue(routineNameDeclaration) |
| 63 | + || isExitWithoutArgs(routineNameDeclaration, node))) { |
| 64 | + checkJumpNode(node, data); |
| 65 | + return data; |
| 66 | + } |
| 67 | + return super.visit(node, data); |
| 68 | + } |
56 | 69 |
|
57 | | - return super.visit(routine, context); |
| 70 | + private static RoutineNameDeclaration getRoutineNameDeclaration(NameReferenceNode node) { |
| 71 | + NameDeclaration nameDeclaration = node.getNameDeclaration(); |
| 72 | + if (nameDeclaration instanceof RoutineNameDeclaration) { |
| 73 | + return (RoutineNameDeclaration) nameDeclaration; |
| 74 | + } |
| 75 | + return null; |
58 | 76 | } |
59 | 77 |
|
60 | | - private void checkBlock(Block block, DelphiCheckContext context) { |
61 | | - if (!(block instanceof UnconditionalJump)) { |
62 | | - return; |
| 78 | + private static boolean isContinue(RoutineNameDeclaration node) { |
| 79 | + String name = node.fullyQualifiedName(); |
| 80 | + return name.equals("System.Continue"); |
| 81 | + } |
| 82 | + |
| 83 | + private static boolean isExitWithoutArgs(RoutineNameDeclaration node, DelphiNode statement) { |
| 84 | + String name = node.fullyQualifiedName(); |
| 85 | + if (!name.equals("System.Exit")) { |
| 86 | + return false; |
63 | 87 | } |
64 | 88 |
|
65 | | - UnconditionalJump jump = (UnconditionalJump) block; |
66 | | - DelphiNode terminator = jump.getTerminator(); |
| 89 | + var argumentList = statement.getParent().getFirstChildOfType(ArgumentListNode.class); |
| 90 | + int arguments = argumentList == null ? 0 : argumentList.getArgumentNodes().size(); |
| 91 | + |
| 92 | + return arguments == 0; |
| 93 | + } |
| 94 | + |
| 95 | + private static Block findBlockWithTerminator(ControlFlowGraph cfg, DelphiNode node) { |
| 96 | + for (Block block : cfg.getBlocks()) { |
| 97 | + if ((block instanceof Terminated) && ((Terminated) block).getTerminator() == node) { |
| 98 | + return block; |
| 99 | + } |
| 100 | + } |
| 101 | + return null; |
| 102 | + } |
67 | 103 |
|
68 | | - RoutineNameDeclaration routineNameDeclaration = getRoutineNameDeclaration(terminator); |
69 | | - if (routineNameDeclaration == null) { |
| 104 | + private void checkJumpNode(NameReferenceNode node, DelphiCheckContext context) { |
| 105 | + ControlFlowGraph cfg = ControlFlowGraphUtils.findContainingCFG(node); |
| 106 | + if (cfg == null) { |
70 | 107 | return; |
71 | 108 | } |
72 | 109 |
|
73 | | - if (!isContinueOrExit(routineNameDeclaration) |
74 | | - || isExitWithExpression(routineNameDeclaration, terminator)) { |
| 110 | + Block terminatedBlock = findBlockWithTerminator(cfg, node); |
| 111 | + if (!(terminatedBlock instanceof UnconditionalJump)) { |
| 112 | + // can't be a redundant jump without a jump |
75 | 113 | return; |
76 | 114 | } |
77 | 115 |
|
| 116 | + UnconditionalJump jump = (UnconditionalJump) terminatedBlock; |
78 | 117 | Block successor = jump.getSuccessor(); |
79 | 118 | if (!successor.equals(jump.getSuccessorIfRemoved())) { |
| 119 | + // without the jump, the successor block would be different |
80 | 120 | return; |
81 | 121 | } |
82 | 122 |
|
83 | | - Block finallyBlock = getFinallyBlock(block); |
84 | | - if (finallyBlock != null) { |
85 | | - if (onlyFinallyBlocksBeforeEnd(finallyBlock)) { |
86 | | - reportIssue(context, terminator, MESSAGE); |
87 | | - } |
88 | | - return; |
| 123 | + if (isViolation(cfg)) { |
| 124 | + reportIssue(context, jump.getTerminator(), MESSAGE); |
89 | 125 | } |
90 | | - |
91 | | - reportIssue(context, terminator, MESSAGE); |
92 | | - } |
93 | | - |
94 | | - private static Block getFinallyBlock(Block block) { |
95 | | - return block.getSuccessors().stream() |
96 | | - .filter(Finally.class::isInstance) |
97 | | - .findFirst() |
98 | | - .orElse(null); |
99 | 126 | } |
100 | 127 |
|
101 | | - private static boolean onlyFinallyBlocksBeforeEnd(Block finallyBlock) { |
102 | | - while (finallyBlock.getSuccessors().size() == 1) { |
103 | | - Block finallySuccessor = finallyBlock.getSuccessors().iterator().next(); |
104 | | - if (!(finallySuccessor instanceof Finally)) { |
| 128 | + private boolean isViolation(ControlFlowGraph cfg) { |
| 129 | + for (TryStatementNode tryFinally : this.tryFinallyStatements) { |
| 130 | + Finally finallyBlock = findFinallyBlock(cfg, tryFinally.getFinallyBlock()); |
| 131 | + if (finallyBlock == null) { |
| 132 | + // if the finally block cannot be found, we have traversed outside the scope of the cfg |
105 | 133 | break; |
106 | 134 | } |
107 | | - finallyBlock = finallySuccessor; |
| 135 | + if (!finallyBlock.getSuccessor().equals(finallyBlock.getExceptionSuccessor())) { |
| 136 | + // multiple paths after the finally corresponds to code that would be skipped from the jump |
| 137 | + return false; |
| 138 | + } |
108 | 139 | } |
109 | | - return finallyBlock.getSuccessors().size() == 1 |
110 | | - && finallyBlock.getSuccessors().iterator().next().getSuccessors().isEmpty(); |
| 140 | + // if no invalidating try-finally blocks are found, the use is a violation |
| 141 | + return true; |
111 | 142 | } |
112 | 143 |
|
113 | | - private static RoutineNameDeclaration getRoutineNameDeclaration(DelphiNode node) { |
114 | | - NameDeclaration nameDeclaration = null; |
115 | | - if (node instanceof NameReferenceNode) { |
116 | | - nameDeclaration = ((NameReferenceNode) node).getNameDeclaration(); |
| 144 | + private static Finally findFinallyBlock(ControlFlowGraph cfg, FinallyBlockNode element) { |
| 145 | + if (element == null) { |
| 146 | + return null; |
117 | 147 | } |
118 | | - if (nameDeclaration instanceof RoutineNameDeclaration) { |
119 | | - return (RoutineNameDeclaration) nameDeclaration; |
| 148 | + for (Block block : cfg.getBlocks()) { |
| 149 | + if ((block instanceof Finally) && ((Finally) block).getTerminator().equals(element)) { |
| 150 | + return (Finally) block; |
| 151 | + } |
120 | 152 | } |
121 | 153 | return null; |
122 | 154 | } |
123 | | - |
124 | | - private static boolean isContinueOrExit(RoutineNameDeclaration routineNameDeclaration) { |
125 | | - String fullyQualifiedName = routineNameDeclaration.fullyQualifiedName(); |
126 | | - return fullyQualifiedName.equals("System.Continue") || fullyQualifiedName.equals("System.Exit"); |
127 | | - } |
128 | | - |
129 | | - private static boolean isExitWithExpression( |
130 | | - RoutineNameDeclaration routineNameDeclaration, DelphiNode statement) { |
131 | | - String fullyQualifiedName = routineNameDeclaration.fullyQualifiedName(); |
132 | | - if (!fullyQualifiedName.equals("System.Exit")) { |
133 | | - return false; |
134 | | - } |
135 | | - |
136 | | - var argumentList = statement.getParent().getFirstChildOfType(ArgumentListNode.class); |
137 | | - int arguments = argumentList == null ? 0 : argumentList.getArgumentNodes().size(); |
138 | | - |
139 | | - return arguments > 0; |
140 | | - } |
141 | 155 | } |
0 commit comments