org.sonar.java.cfg.CFGLoop Maven / Gradle / Ivy
The newest version!
/*
* SonarQube Java
* Copyright (C) 2012-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.cfg;
import org.sonar.java.annotations.VisibleForTesting;
import org.sonar.java.cfg.CFG.Block;
import org.sonar.plugins.java.api.tree.Tree;
import javax.annotation.CheckForNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class CFGLoop {
private final CFG.Block startingBlock;
private final Set blocks = new HashSet<>();
private final Set successors = new HashSet<>();
private CFGLoop(CFG.Block block) {
startingBlock = block;
}
private void initialize(CFG.Block block, Map container) {
Block loopFirstBlock = block.trueBlock();
if (loopFirstBlock == null) {
// Special case where no condition is given in FOR loop: only one successor specified
loopFirstBlock = block.successors().iterator().next();
}
collectBlocks(loopFirstBlock, container);
successors.addAll(block.successors());
successors.remove(block.falseBlock());
collectWaysOut(container);
}
@VisibleForTesting
CFG.Block startingBlock() {
return startingBlock;
}
@VisibleForTesting
Collection blocks() {
return new ArrayList<>(blocks);
}
@VisibleForTesting
Collection successors() {
return new ArrayList<>(successors);
}
public boolean hasNoWayOut() {
return successors.isEmpty();
}
private void collectBlocks(CFG.Block block, Map container) {
collectBlocks(block, container, new HashSet<>());
}
private boolean collectBlocks(CFG.Block block, Map container, Set visitedBlocks) {
if (blocks.contains(block)) {
return true;
}
if (block.id() == startingBlock.id() || !visitedBlocks.add(block)) {
return false;
}
boolean answer = returnsToStart(block, container, visitedBlocks);
if (answer || isBreak(block)) {
blocks.add(block);
}
return answer;
}
private boolean returnsToStart(CFG.Block block, Map container, Set visitedBlocks) {
Set localSuccessors = localSuccessors(block, container);
if (localSuccessors == null) {
return true;
}
boolean answer = false;
for (CFG.Block successor : localSuccessors) {
if (startingBlock.id() == successor.id()) {
answer = true;
} else {
answer |= collectBlocks(successor, container, visitedBlocks);
}
}
return answer;
}
@CheckForNull
private static Set localSuccessors(CFG.Block block, Map container) {
if (isStarting(block)) {
CFGLoop loop = container.get(block.terminator());
if (loop == null) {
loop = create(block, container);
}
Set loopSuccessors = new HashSet<>(loop.successors);
if (block.trueBlock() == null) {
// Special case where no condition is given in FOR loop: only one successor specified
return null;
} else {
loopSuccessors.add(block.falseBlock());
}
return loopSuccessors;
}
return block.successors();
}
private static boolean isBreak(CFG.Block block) {
Tree terminator = block.terminator();
return terminator != null && terminator.is(Tree.Kind.BREAK_STATEMENT);
}
private void collectWaysOut(Map container) {
for (CFG.Block block : blocks) {
if (isStarting(block)) {
CFGLoop innerLoop = container.get(block.terminator());
successors.addAll(innerLoop.successors());
} else {
successors.addAll(block.successors());
}
}
successors.removeAll(blocks);
successors.remove(startingBlock);
}
private static boolean isStarting(CFG.Block block) {
Tree terminator = block.terminator();
return terminator != null && terminator.is(Tree.Kind.FOR_STATEMENT, Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT);
}
public static Map getCFGLoops(CFG cfg) {
Map cfgLoops = new HashMap<>();
for (CFG.Block block : cfg.blocks()) {
if (CFGLoop.isStarting(block)) {
Tree terminator = block.terminator();
if (!cfgLoops.containsKey(terminator)) {
create(block, cfgLoops);
}
}
}
return cfgLoops;
}
private static CFGLoop create(CFG.Block block, Map container) {
CFGLoop loop = new CFGLoop(block);
container.put(block.terminator(), loop);
loop.initialize(block, container);
return loop;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy