All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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