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

org.sonar.java.cfg.CFG 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 java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.java.Preconditions;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.java.model.JavaTree;
import org.sonar.plugins.java.api.cfg.ControlFlowGraph;
import org.sonar.plugins.java.api.location.Position;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ArrayAccessExpressionTree;
import org.sonar.plugins.java.api.tree.ArrayDimensionTree;
import org.sonar.plugins.java.api.tree.AssertStatementTree;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.BinaryExpressionTree;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.BreakStatementTree;
import org.sonar.plugins.java.api.tree.CaseGroupTree;
import org.sonar.plugins.java.api.tree.CaseLabelTree;
import org.sonar.plugins.java.api.tree.CatchTree;
import org.sonar.plugins.java.api.tree.ConditionalExpressionTree;
import org.sonar.plugins.java.api.tree.ContinueStatementTree;
import org.sonar.plugins.java.api.tree.DoWhileStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.ForStatementTree;
import org.sonar.plugins.java.api.tree.GuardedPatternTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.IfStatementTree;
import org.sonar.plugins.java.api.tree.InstanceOfTree;
import org.sonar.plugins.java.api.tree.LabeledStatementTree;
import org.sonar.plugins.java.api.tree.ListTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.NewArrayTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.NullPatternTree;
import org.sonar.plugins.java.api.tree.ParenthesizedTree;
import org.sonar.plugins.java.api.tree.PatternInstanceOfTree;
import org.sonar.plugins.java.api.tree.PatternTree;
import org.sonar.plugins.java.api.tree.RecordPatternTree;
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
import org.sonar.plugins.java.api.tree.StatementTree;
import org.sonar.plugins.java.api.tree.SwitchExpressionTree;
import org.sonar.plugins.java.api.tree.SwitchStatementTree;
import org.sonar.plugins.java.api.tree.SwitchTree;
import org.sonar.plugins.java.api.tree.SynchronizedStatementTree;
import org.sonar.plugins.java.api.tree.SyntaxToken;
import org.sonar.plugins.java.api.tree.ThrowStatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TryStatementTree;
import org.sonar.plugins.java.api.tree.TypeCastTree;
import org.sonar.plugins.java.api.tree.TypePatternTree;
import org.sonar.plugins.java.api.tree.TypeTree;
import org.sonar.plugins.java.api.tree.UnaryExpressionTree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonar.plugins.java.api.tree.WhileStatementTree;
import org.sonar.plugins.java.api.tree.YieldStatementTree;
import org.sonarsource.analyzer.commons.collections.ListUtils;

public class CFG implements ControlFlowGraph {

  private static final Logger LOG = LoggerFactory.getLogger(CFG.class);

  private final boolean ignoreBreakAndContinue;
  @Nullable
  private Symbol.MethodSymbol methodSymbol;
  private Block currentBlock;
  private boolean hasCompleteSemantic = true;

  /**
   * List of all blocks in order they were created.
   */
  private final List blocks = new ArrayList<>();

  private final Deque breakTargets = new LinkedList<>();
  private final Deque continueTargets = new LinkedList<>();
  private final Deque exitBlocks = new LinkedList<>();
  private final Deque enclosingTry = new LinkedList<>();
  private final Deque enclosedByCatch = new LinkedList<>();
  private final TryStatement outerTry;

  private static class TryStatement {
    Map catches = new LinkedHashMap<>();
    List runtimeCatches = new ArrayList<>();

    public void addCatch(Type type, Block catchBlock) {
      if (type.is("java.lang.Exception")
        || type.is("java.lang.Throwable")
        || type.isSubtypeOf("java.lang.Error")
        || type.isSubtypeOf("java.lang.RuntimeException")
        || !type.isSubtypeOf("java.lang.Exception")) {
        runtimeCatches.add(catchBlock);
      }
      catches.put(type, catchBlock);
    }
  }

  private String pendingLabel = null;
  private Map labelsBreakTarget = new HashMap<>();
  private Map labelsContinueTarget = new HashMap<>();

  private CFG(List trees, @Nullable MethodTree tree, boolean ignoreBreakAndContinue) {
    if (tree != null) {
      methodSymbol = tree.symbol();
      checkSymbolSemantic(methodSymbol, "method definition", tree.simpleName().identifierToken());
    }
    this.ignoreBreakAndContinue = ignoreBreakAndContinue;
    exitBlocks.add(createBlock());
    currentBlock = createBlock(exitBlock());
    outerTry = new TryStatement();
    enclosingTry.add(outerTry);
    enclosedByCatch.push(false);
    build(trees);
    prune();
    computePredecessors(blocks);
  }

  @Override
  public boolean hasCompleteSemantic() {
    return hasCompleteSemantic;
  }

  private void checkIdentifierSemantic(IdentifierTree tree) {
    checkSymbolSemantic(tree.symbol(), "unknown identifier", tree.identifierToken());
  }

  private void checkSymbolSemantic(@Nullable Symbol symbol, String reason, @Nullable SyntaxToken location) {
    if (symbol != null) {
      if (symbol.isUnknown()) {
        markSemanticAsIncomplete(reason, location);
      } else if (symbol instanceof Symbol.MethodSymbol method) {
        if (method.returnType().isUnknown()) {
          markSemanticAsIncomplete("unknown return type", location);
        } else if (method.parameterTypes().stream().anyMatch(Type::isUnknown)) {
          markSemanticAsIncomplete("unknown parameter type", location);
        }
      }
    }
  }

  private Type checkTypeSemantic(Type type, String reason, @Nullable SyntaxToken location) {
    if (type.isUnknown()) {
      markSemanticAsIncomplete(reason, location);
    }
    return type;
  }

  private void markSemanticAsIncomplete(String reason, @Nullable SyntaxToken location) {
    if (hasCompleteSemantic) {
      hasCompleteSemantic = false;
      if (LOG.isDebugEnabled()) {
        StringBuilder logMessage = new StringBuilder();
        logMessage.append("Incomplete Semantic, ").append(reason);
        if (location != null) {
          logMessage.append(" '").append(location.text()).append("'");
          Position position = location.range().start();
          logMessage.append(" line ").append(position.line()).append(" col ").append(position.column());
        }
        LOG.debug(logMessage.toString());
      }
    }
  }

  @Override
  public Block exitBlock() {
    return exitBlocks.peek();
  }

  @Nullable
  public Symbol.MethodSymbol methodSymbol() {
    return methodSymbol;
  }

  @Override
  public Block entryBlock() {
    return currentBlock;
  }

  @Override
  public List blocks() {
    return ListUtils.reverse(blocks);
  }

  public List reversedBlocks() {
    return blocks;
  }

  public interface IBlock {
    int id();
    List elements();

    T terminator();

    Set> successors();
  }

  public static class Block implements IBlock, ControlFlowGraph.Block {
    public static final Predicate IS_CATCH_BLOCK = Block::isCatchBlock;
    private int id;
    private final List elements = new ArrayList<>();
    private final Set successors = new LinkedHashSet<>();
    private final Set predecessors = new LinkedHashSet<>();
    private final Set exceptions = new LinkedHashSet<>();
    private Block trueBlock;
    private Block falseBlock;
    private Block exitBlock;
    private Block successorWithoutJump;

    private Tree terminator;
    private CaseGroupTree caseGroup;

    private boolean isFinallyBlock;
    private boolean isCatchBlock = false;
    private boolean isDefaultBlock = false;

    public Block(int id) {
      this.id = id;
    }

    @Override
    public int id() {
      return id;
    }

    @Override
    public List elements() {
      return ListUtils.reverse(elements);
    }

    @Override
    public Block trueBlock() {
      return trueBlock;
    }

    @Override
    public Block falseBlock() {
      return falseBlock;
    }

    @Override
    public Block exitBlock() {
      return exitBlock;
    }

    @Override
    public boolean isFinallyBlock() {
      return isFinallyBlock;
    }

    @Override
    public boolean isCatchBlock() {
      return isCatchBlock;
    }

    @Override
    public boolean isDefaultBlock() {
      return isDefaultBlock;
    }

    void addSuccessor(Block successor) {
      successors.add(successor);
    }

    public void addTrueSuccessor(Block successor) {
      if (trueBlock != null) {
        throw new IllegalStateException("Attempt to re-assign a true successor");
      }
      successors.add(successor);
      trueBlock = successor;
    }

    public void addFalseSuccessor(Block successor) {
      if (falseBlock != null) {
        throw new IllegalStateException("Attempt to re-assign a false successor");
      }
      successors.add(successor);
      falseBlock = successor;
    }

    public void addExitSuccessor(Block block) {
      successors.add(block);
      exitBlock = block;
    }

    public Set predecessors() {
      return predecessors;
    }

    @Override
    public Set successors() {
      return successors;
    }

    @Override
    public Set exceptions() {
      return exceptions;
    }

    @Override
    @CheckForNull
    public Tree terminator() {
      return terminator;
    }

    public boolean isInactive() {
      return terminator == null && elements.isEmpty() && successors.size() == 1;
    }

    private void prune(Block inactiveBlock) {
      boolean hasUniqueSuccessor = inactiveBlock.successors.size() == 1;
      if (inactiveBlock.equals(trueBlock)) {
        Preconditions.checkArgument(hasUniqueSuccessor, "True successor must be replaced by a unique successor!");
        trueBlock = inactiveBlock.successors.iterator().next();
      }
      if (inactiveBlock.equals(falseBlock)) {
        Preconditions.checkArgument(hasUniqueSuccessor, "False successor must be replaced by a unique successor!");
        falseBlock = inactiveBlock.successors.iterator().next();
      }
      if (inactiveBlock.equals(successorWithoutJump)) {
        Preconditions.checkArgument(hasUniqueSuccessor, "SuccessorWithoutJump successor must be replaced by a unique successor!");
        successorWithoutJump = inactiveBlock.successors.iterator().next();
      }
      if (successors.remove(inactiveBlock)) {
        successors.addAll(inactiveBlock.successors);
      }
      if (exceptions.remove(inactiveBlock)) {
        exceptions.addAll(inactiveBlock.exceptions);
        exceptions.addAll(inactiveBlock.successors);
      }
      if (inactiveBlock.equals(exitBlock)) {
        exitBlock = inactiveBlock.successors.iterator().next();
      }
    }

    public boolean isMethodExitBlock() {
      return successors().isEmpty();
    }

    /**
     * This method makes the implementation of RSPEC-3626 almost trivial.
     * @return the block which would be the successor of this one if this one didn't terminate with a jump
     */
    @CheckForNull
    public Block successorWithoutJump() {
      return successorWithoutJump;
    }

    /**
     * Label is used to contain additional information about a block which is not directly related to CFG structure.
     * Used for simplifying implementation of RSPEC-128.
     */
    @CheckForNull
    @Override
    public CaseGroupTree caseGroup() {
      return caseGroup;
    }

    public void setCaseGroup(CaseGroupTree caseGroup) {
      this.caseGroup = caseGroup;
    }
  }

  private static void computePredecessors(List blocks) {
    for (Block b : blocks) {
      for (Block successor : b.successors) {
        successor.predecessors.add(b);
      }
      for (Block successor : b.exceptions) {
        successor.predecessors.add(b);
      }
    }
    cleanupUnfeasibleBreakPaths(blocks);
  }

  private static void cleanupUnfeasibleBreakPaths(List blocks) {
    for (Block block : blocks) {
      Set happyPathPredecessor = block.predecessors.stream().filter(p -> !p.exceptions.contains(block)).collect(Collectors.toSet());
      if(block.isFinallyBlock && happyPathPredecessor.size() == 1) {
        Block pred = happyPathPredecessor.iterator().next();
        if (pred.terminator != null && pred.terminator.is(Tree.Kind.BREAK_STATEMENT)) {
          Set succs = block.successors.stream()
            .map(suc -> isLoop(suc) ? getAfterLoopBlock(suc) : suc)
            .filter(Objects::nonNull)
            .collect(Collectors.toSet());
          block.successors.clear();
          block.successors.addAll(succs);
        }
      }
    }
  }

  private static boolean isLoop(Block successor) {
    return successor.terminator != null
      && successor.terminator.is(Tree.Kind.WHILE_STATEMENT, Tree.Kind.DO_STATEMENT, Tree.Kind.FOR_STATEMENT, Tree.Kind.FOR_EACH_STATEMENT);
  }

  @CheckForNull
  private static Block getAfterLoopBlock(Block loop) {
    if (loop.falseBlock != null) {
      return loop.falseBlock;
    }
    // Because 'for' statements without condition are unconditional jumps block
    return loop.successorWithoutJump;
  }

  private void prune() {
    List inactiveBlocks = new ArrayList<>();
    boolean first = true;
    for (Block block : blocks) {
      if (!first && isInactive(block)) {
        inactiveBlocks.add(block);
      }
      first = false;
    }
    if (!inactiveBlocks.isEmpty()) {
      removeInactiveBlocks(inactiveBlocks);
      if (inactiveBlocks.contains(currentBlock)) {
        currentBlock = currentBlock.successors.iterator().next();
      }
      int id = 0;
      for (Block block : blocks) {
        block.id = id;
        id += 1;
      }
      inactiveBlocks.removeAll(blocks);
      if (!inactiveBlocks.isEmpty()) {
        prune();
      }
    }
  }

  private boolean isInactive(Block block) {
    if (block.equals(currentBlock) && block.successors.size() > 1) {
      return false;
    }
    return block.isInactive();
  }

  private void removeInactiveBlocks(List inactiveBlocks) {
    for (Block inactiveBlock : inactiveBlocks) {
      for (Block block : blocks) {
        block.prune(inactiveBlock);
      }
    }
    blocks.removeAll(inactiveBlocks);
  }

  private Block createBlock(Block successor) {
    Block result = createBlock();
    result.addSuccessor(successor);
    return result;
  }

  private Block createBlock() {
    Block result = new Block(blocks.size());
    blocks.add(result);
    return result;
  }

  public static CFG buildCFG(List trees, boolean ignoreBreak) {
    return new CFG(trees, null, ignoreBreak);
  }

  public static CFG buildCFG(List trees) {
    return new CFG(trees, null, false);
  }

  public static CFG build(MethodTree tree) {
    BlockTree block = tree.block();
    Preconditions.checkArgument(block != null, "Cannot build CFG for method with no body.");
    return new CFG(block.body(), tree, false);
  }

  private void build(ListTree trees) {
    build((List) trees);
  }

  private void build(List trees) {
    ListUtils.reverse(trees).forEach(this::build);
  }

  private void build(Tree tree) {
    switch (tree.kind()) {
      case BLOCK:
        build(((BlockTree) tree).body());
        break;
      case RETURN_STATEMENT:
        buildReturnStatement((ReturnStatementTree) tree);
        break;
      case EXPRESSION_STATEMENT:
        build(((ExpressionStatementTree) tree).expression());
        break;
      case METHOD_INVOCATION:
        buildMethodInvocation((MethodInvocationTree) tree);
        break;
      case IF_STATEMENT:
        buildIfStatement((IfStatementTree) tree);
        break;
      case CONDITIONAL_EXPRESSION:
        buildConditionalExpression((ConditionalExpressionTree) tree);
        break;
      case VARIABLE:
        buildVariable((VariableTree) tree);
        break;
      case MULTIPLY,
        DIVIDE,
        REMAINDER,
        PLUS,
        MINUS,
        LEFT_SHIFT,
        RIGHT_SHIFT,
        UNSIGNED_RIGHT_SHIFT,
        AND,
        XOR,
        OR,
        GREATER_THAN,
        GREATER_THAN_OR_EQUAL_TO,
        LESS_THAN,
        LESS_THAN_OR_EQUAL_TO,
        EQUAL_TO,
        NOT_EQUAL_TO:
        buildBinaryExpression(tree);
        break;
      case ASSIGNMENT,
        LEFT_SHIFT_ASSIGNMENT,
        RIGHT_SHIFT_ASSIGNMENT,
        AND_ASSIGNMENT,
        REMAINDER_ASSIGNMENT,
        UNSIGNED_RIGHT_SHIFT_ASSIGNMENT,
        OR_ASSIGNMENT,
        XOR_ASSIGNMENT,
        DIVIDE_ASSIGNMENT,
        MULTIPLY_ASSIGNMENT,
        PLUS_ASSIGNMENT,
        MINUS_ASSIGNMENT:
        buildAssignment((AssignmentExpressionTree) tree);
        break;
      case MEMBER_SELECT:
        buildMemberSelect((MemberSelectExpressionTree) tree);
        break;
      case CONDITIONAL_AND:
        buildConditionalAnd((BinaryExpressionTree) tree);
        break;
      case CONDITIONAL_OR:
        buildConditionalOr((BinaryExpressionTree) tree);
        break;
      case LABELED_STATEMENT:
        buildLabeledStatement((LabeledStatementTree) tree);
        break;
      case SWITCH_STATEMENT:
        buildSwitchStatement((SwitchStatementTree) tree);
        break;
      case SWITCH_EXPRESSION:
        buildSwitchExpression((SwitchExpressionTree) tree);
        break;
      case BREAK_STATEMENT:
        buildBreakStatement((BreakStatementTree) tree);
        break;
      case YIELD_STATEMENT:
        buildYieldStatement((YieldStatementTree) tree);
        break;
      case CONTINUE_STATEMENT:
        buildContinueStatement((ContinueStatementTree) tree);
        break;
      case WHILE_STATEMENT:
        buildWhileStatement((WhileStatementTree) tree);
        break;
      case DO_STATEMENT:
        buildDoWhileStatement((DoWhileStatementTree) tree);
        break;
      case FOR_EACH_STATEMENT:
        buildForEachStatement((ForEachStatement) tree);
        break;
      case FOR_STATEMENT:
        buildForStatement((ForStatementTree) tree);
        break;
      case TRY_STATEMENT:
        buildTryStatement((TryStatementTree) tree);
        break;
      case THROW_STATEMENT:
        buildThrowStatement((ThrowStatementTree) tree);
        break;
      case SYNCHRONIZED_STATEMENT:
        buildSynchronizedStatement((SynchronizedStatementTree) tree);
        break;
      case POSTFIX_INCREMENT,
        POSTFIX_DECREMENT,
        PREFIX_INCREMENT,
        PREFIX_DECREMENT,
        UNARY_MINUS,
        UNARY_PLUS,
        BITWISE_COMPLEMENT,
        LOGICAL_COMPLEMENT:
        buildUnaryExpression((UnaryExpressionTree) tree);
        break;
      case PARENTHESIZED_EXPRESSION:
        build(((ParenthesizedTree) tree).expression());
        break;
      case ARRAY_ACCESS_EXPRESSION:
        buildArrayAccessExpression((ArrayAccessExpressionTree) tree);
        break;
      case ARRAY_DIMENSION:
        buildArrayDimension((ArrayDimensionTree) tree);
        break;
      case NEW_CLASS:
        buildNewClass((NewClassTree) tree);
        break;
      case TYPE_CAST:
        buildTypeCast(tree);
        break;
      case INSTANCE_OF:
        buildInstanceOf((InstanceOfTree) tree);
        break;
      case PATTERN_INSTANCE_OF:
        buildInstanceOf((PatternInstanceOfTree) tree);
        break;
      case NEW_ARRAY:
        buildNewArray((NewArrayTree) tree);
        break;
      // assert can be ignored by VM so skip them for now.
      case ASSERT_STATEMENT:
        buildAssertStatement((AssertStatementTree) tree);
        break;
      // store declarations as complete blocks.
      case EMPTY_STATEMENT,
        CLASS,
        RECORD,
        ENUM,
        ANNOTATION_TYPE,
        INTERFACE,
        METHOD_REFERENCE,
        LAMBDA_EXPRESSION,
        // simple instructions
        INT_LITERAL,
        LONG_LITERAL,
        DOUBLE_LITERAL,
        CHAR_LITERAL,
        FLOAT_LITERAL,
        STRING_LITERAL,
        TEXT_BLOCK,
        BOOLEAN_LITERAL,
        NULL_LITERAL:
        currentBlock.elements.add(tree);
        break;
      case IDENTIFIER:
        checkIdentifierSemantic((IdentifierTree) tree);
        currentBlock.elements.add(tree);
        break;
      case NULL_PATTERN,
        TYPE_PATTERN,
        GUARDED_PATTERN,
        RECORD_PATTERN,
        DEFAULT_PATTERN:
        buildPattern((PatternTree) tree);
        break;
      default:
        throw new UnsupportedOperationException(tree.kind().name() + " " + ((JavaTree) tree).getLine());
    }
  }

  private void buildReturnStatement(ReturnStatementTree returnStatement) {
    currentBlock = createUnconditionalJump(returnStatement, exitBlock(), currentBlock);
    ExpressionTree expression = returnStatement.expression();
    if (expression != null) {
      build(expression);
    }
  }

  private void buildMethodInvocation(MethodInvocationTree mit) {
    SyntaxToken location = mit.methodSelect().lastToken();
    checkSymbolSemantic(mit.methodSymbol(), "method invocation", location);
    handleExceptionalPaths(mit.methodSymbol(), location);
    currentBlock.elements.add(mit);
    build(mit.arguments());
    if (mit.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
      MemberSelectExpressionTree memberSelect = (MemberSelectExpressionTree) mit.methodSelect();
      build(memberSelect.expression());
    } else {
      build(mit.methodSelect());
    }
  }

  private void buildIfStatement(IfStatementTree ifStatementTree) {
    Block next = currentBlock;
    // process else-branch
    Block elseBlock = next;
    StatementTree elseStatement = ifStatementTree.elseStatement();
    if (elseStatement != null) {
      // if statement will create the required block.
      if (!elseStatement.is(Tree.Kind.IF_STATEMENT)) {
        currentBlock = createBlock(next);
      }
      build(elseStatement);
      elseBlock = currentBlock;
    }
    // process then-branch
    currentBlock = createBlock(next);
    build(ifStatementTree.thenStatement());
    Block thenBlock = currentBlock;
    // process condition
    currentBlock = createBranch(ifStatementTree, thenBlock, elseBlock);
    buildCondition(ifStatementTree.condition(), thenBlock, elseBlock);
  }

  private void buildConditionalExpression(ConditionalExpressionTree cond) {
    Block next = currentBlock;
    // process else-branch
    ExpressionTree elseStatement = cond.falseExpression();
    currentBlock = createBlock(next);
    build(elseStatement);
    Block elseBlock = currentBlock;
    // process then-branch
    currentBlock = createBlock(next);
    build(cond.trueExpression());
    Block thenBlock = currentBlock;
    // process condition
    currentBlock = createBranch(cond, thenBlock, elseBlock);
    buildCondition(cond.condition(), thenBlock, elseBlock);
  }

  private void buildVariable(VariableTree tree) {
    checkTypeSemantic(tree.type().symbolType(), "unknown variable type", tree.type().firstToken());
    currentBlock.elements.add(tree);
    ExpressionTree initializer = tree.initializer();
    if (initializer != null) {
      build(initializer);
    }
  }

  private void buildBinaryExpression(Tree tree) {
    BinaryExpressionTree binaryExpressionTree = (BinaryExpressionTree) tree;
    currentBlock.elements.add(tree);
    build(binaryExpressionTree.rightOperand());
    build(binaryExpressionTree.leftOperand());
  }

  private void buildAssignment(AssignmentExpressionTree tree) {
    currentBlock.elements.add(tree);
    build(tree.expression());
    // The variable is not evaluated for simple assignment as it's only used to know where to store the value: JLS8-15.26
    if (!ExpressionUtils.isSimpleAssignment(tree)) {
      build(tree.variable());
    }
  }

  private void buildMemberSelect(MemberSelectExpressionTree mse) {
    currentBlock.elements.add(mse);
    // int.class or String[].class are memberSelectExpression which expression part is not an expression.
    if (!"class".equals(mse.identifier().name())) {
      build(mse.expression());
    }
  }

  private void buildConditionalAnd(BinaryExpressionTree tree) {
    Block falseBlock = currentBlock;
    currentBlock = createBlock(falseBlock);
    // process RHS
    build(tree.rightOperand());
    // process LHS
    buildConditionalBinaryLHS(tree, currentBlock, falseBlock);
  }

  private void buildConditionalOr(BinaryExpressionTree tree) {
    Block trueBlock = currentBlock;
    currentBlock = createBlock(trueBlock);
    // process RHS
    build(tree.rightOperand());
    // process LHS
    buildConditionalBinaryLHS(tree, trueBlock, currentBlock);
  }

  private void buildConditionalBinaryLHS(BinaryExpressionTree tree, Block trueBlock, Block falseBlock) {
    currentBlock = createBlock();
    Block toComplete = currentBlock;
    build(tree.leftOperand());
    toComplete.terminator = tree;
    toComplete.addFalseSuccessor(falseBlock);
    toComplete.addTrueSuccessor(trueBlock);
  }

  private void buildLabeledStatement(LabeledStatementTree labeledStatement) {
    String name = labeledStatement.label().name();
    labelsBreakTarget.put(name, currentBlock);
    pendingLabel = name;
    currentBlock = createBlock(currentBlock);
    build(labeledStatement.statement());
    currentBlock = createBlock(currentBlock);
  }

  private void buildSwitchStatement(SwitchStatementTree switchStatementTree) {
    buildSwitch(switchStatementTree, switchStatementTree);
  }

  private void buildSwitchExpression(SwitchExpressionTree switchExpressionTree) {
    buildSwitch(switchExpressionTree, switchExpressionTree);
  }

  private void buildSwitch(SwitchTree switchTree, Tree terminator) {
    Block switchSuccessor = currentBlock;
    // process condition
    currentBlock = createBlock();
    currentBlock.terminator = terminator;
    Block switchBlock = currentBlock;
    List switchCasesExpressions = switchTree.cases()
      .stream()
      .map(CaseGroupTree::labels)
      .flatMap(List::stream)
      .map(CaseLabelTree::expressions)
      .flatMap(List::stream).toList();
    ListUtils.reverse(switchCasesExpressions).forEach(this::build);

    build(switchTree.expression());
    Block conditionBlock = currentBlock;
    // process body
    currentBlock = createBlock(switchSuccessor);
    breakTargets.addLast(switchSuccessor);
    boolean hasDefaultCase = false;
    if (!switchTree.cases().isEmpty()) {
      boolean withoutFallTrough = switchWithoutFallThrough(switchTree);
      CaseGroupTree firstCase = switchTree.cases().get(0);
      for (CaseGroupTree caseGroupTree : ListUtils.reverse(switchTree.cases())) {
        if (withoutFallTrough) {
          currentBlock.successors().clear();
          currentBlock.addSuccessor(switchSuccessor);
        }
        build(caseGroupTree.body());
        currentBlock.elements.add(caseGroupTree);
        if (!hasDefaultCase) {
          hasDefaultCase = containsDefaultCase(caseGroupTree.labels());
          currentBlock.isDefaultBlock = hasDefaultCase;
        }
        currentBlock.setCaseGroup(caseGroupTree);
        switchBlock.addSuccessor(currentBlock);
        if (!caseGroupTree.equals(firstCase)) {
          // No block preceding the first case group.
          currentBlock = createBlock(currentBlock);
        }
      }
    }
    breakTargets.removeLast();
    // process condition
    currentBlock = switchBlock;
    if (!hasDefaultCase && !terminator.is(Tree.Kind.SWITCH_EXPRESSION)) {
      currentBlock.addSuccessor(switchSuccessor);
    }
    currentBlock = conditionBlock;
  }

  /**
   * A switch expression can use the traditional cases with 'colon' (with fall-through) or,
   * starting with java 12, the 'arrow' cases (without fall-through). Cases can not be mixed.
   *
   * @param switchTree the switch to evaluate
   * @return true if the switch uses fall-through
   */
  private static boolean switchWithoutFallThrough(SwitchTree switchTree) {
    return switchTree.cases().stream()
      .map(CaseGroupTree::labels)
      .flatMap(List::stream)
      .noneMatch(CaseLabelTree::isFallThrough);
  }

  private static boolean containsDefaultCase(List labels) {
    return labels.stream().anyMatch(caseLabel -> "default".equals(caseLabel.caseOrDefaultKeyword().text())
      // JDK 17 preview feature
      || caseLabel.expressions().stream().anyMatch(expr -> expr.is(Tree.Kind.DEFAULT_PATTERN)));
  }

  private void buildBreakStatement(BreakStatementTree tree) {
    IdentifierTree label = tree.label();
    boolean isLabel = false;
    Block targetBlock = null;
    if (label == null) {
      if (breakTargets.isEmpty()) {
        if (!ignoreBreakAndContinue) {
          throw new IllegalStateException("'break' statement not in loop or switch statement");
        }
      } else {
        targetBlock = breakTargets.getLast();
      }
    } else {
      isLabel = label.symbol() instanceof Symbol.LabelSymbol;
      if (isLabel) {
        targetBlock = labelsBreakTarget.get(label.name());
      } else {
        targetBlock = breakTargets.getLast();
      }
    }
    currentBlock = createUnconditionalJump(tree, targetBlock, currentBlock);
    if(currentBlock.exitBlock != null) {
      currentBlock.exitBlock = null;
    }
  }

  private void buildYieldStatement(YieldStatementTree tree) {
    currentBlock = createUnconditionalJump(tree, breakTargets.isEmpty() ? null : breakTargets.getLast(), currentBlock);
    build(tree.expression());
    currentBlock.exitBlock = null;
  }

  private void buildContinueStatement(ContinueStatementTree tree) {
    IdentifierTree label = tree.label();
    Block targetBlock = null;
    if (label == null) {
      if (continueTargets.isEmpty()) {
        if (!ignoreBreakAndContinue) {
          throw new IllegalStateException("'continue' statement not in loop or switch statement");
        }
      } else {
        targetBlock = continueTargets.getLast();
      }
    } else {
      targetBlock = labelsContinueTarget.get(label.name());
    }
    currentBlock = createUnconditionalJump(tree, targetBlock, currentBlock);
    // cleanup for continue statement to a finally: continue block can't have an exit block.
    currentBlock.exitBlock = null;
  }

  private void buildWhileStatement(WhileStatementTree whileStatement) {
    Block falseBranch = currentBlock;
    Block loopback = createBlock();
    // process body
    currentBlock = createBlock(loopback);
    addContinueTarget(loopback);
    breakTargets.addLast(falseBranch);
    build(whileStatement.statement());
    breakTargets.removeLast();
    continueTargets.removeLast();
    Block bodyBlock = currentBlock;
    // process condition
    currentBlock = createBranch(whileStatement, bodyBlock, falseBranch);
    buildCondition(whileStatement.condition(), bodyBlock, falseBranch);
    loopback.addSuccessor(currentBlock);
    currentBlock = createBlock(currentBlock);
  }

  private void buildDoWhileStatement(DoWhileStatementTree doWhileStatementTree) {
    Block falseBranch = currentBlock;
    Block loopback = createBlock();
    // process condition
    currentBlock = createBranch(doWhileStatementTree, loopback, falseBranch);
    buildCondition(doWhileStatementTree.condition(), loopback, falseBranch);
    // process body
    addContinueTarget(currentBlock);
    currentBlock = createBlock(currentBlock);
    breakTargets.addLast(falseBranch);
    build(doWhileStatementTree.statement());
    breakTargets.removeLast();
    continueTargets.removeLast();
    loopback.addSuccessor(currentBlock);
    currentBlock = createBlock(currentBlock);
  }

  private void buildForEachStatement(ForEachStatement tree) {
    // TODO(npe) One solution is to create a forstatement node depending on type of expression (iterable or array) and build CFG from it.
    Block afterLoop = currentBlock;
    Block statementBlock = createBlock();
    Block loopback = createBranch(tree, statementBlock, afterLoop);
    currentBlock = createBlock(loopback);
    addContinueTarget(loopback);
    breakTargets.addLast(afterLoop);
    build(tree.statement());
    breakTargets.removeLast();
    continueTargets.removeLast();
    statementBlock.addSuccessor(currentBlock);
    currentBlock = loopback;
    build(tree.variable());
    currentBlock = createBlock(currentBlock);
    build(tree.expression());
    currentBlock = createBlock(currentBlock);
  }

  private void addContinueTarget(Block target) {
    continueTargets.addLast(target);
    if (pendingLabel != null) {
      labelsContinueTarget.put(pendingLabel, target);
      pendingLabel = null;
    }
  }

  private void buildForStatement(ForStatementTree tree) {
    Block falseBranch = currentBlock;
    // process step
    currentBlock = createBlock();
    Block updateBlock = currentBlock;
    build(tree.update());
    addContinueTarget(currentBlock);
    // process body
    currentBlock = createBlock(currentBlock);
    breakTargets.addLast(falseBranch);
    build(tree.statement());
    breakTargets.removeLast();
    continueTargets.removeLast();
    Block body = currentBlock;
    // process condition
    ExpressionTree condition = tree.condition();
    if (condition != null) {
      currentBlock = createBranch(tree, body, falseBranch);
      buildCondition(condition, body, falseBranch);
    } else {
      currentBlock = createUnconditionalJump(tree, body, falseBranch);
    }
    updateBlock.addSuccessor(currentBlock);
    // process init
    currentBlock = createBlock(currentBlock);
    build(tree.initializer());
  }

  private void buildTryStatement(TryStatementTree tryStatementTree) {
    // FIXME only path with no failure constructed for now, (not taking try with resources into consideration).
    currentBlock = createBlock(currentBlock);
    BlockTree finallyBlockTree = tryStatementTree.finallyBlock();
    if (finallyBlockTree != null) {
      currentBlock.isFinallyBlock = true;
      Block finallyBlock = currentBlock;
      build(finallyBlockTree);
      finallyBlock.addExitSuccessor(exitBlock());
      exitBlocks.push(currentBlock);
      addContinueTarget(currentBlock);
      currentBlock.isFinallyBlock = true;
      breakTargets.addLast(currentBlock);
    }
    Block finallyOrEndBlock = currentBlock;
    Block beforeFinally = createBlock(currentBlock);
    TryStatement tryStatement = new TryStatement();
    enclosingTry.push(tryStatement);
    enclosedByCatch.push(false);
    for (CatchTree catchTree : ListUtils.reverse(tryStatementTree.catches())) {
      currentBlock = createBlock(finallyOrEndBlock);
      enclosedByCatch.push(true);
      build(catchTree.block());
      buildVariable(catchTree.parameter());
      currentBlock.isCatchBlock = true;
      enclosedByCatch.pop();
      TypeTree type = catchTree.parameter().type();
      tryStatement.addCatch(checkTypeSemantic(type.symbolType(), "catch parameter type", type.firstToken()), currentBlock);
    }
    currentBlock = beforeFinally;
    build(tryStatementTree.block());
    build((List) tryStatementTree.resourceList());
    enclosingTry.pop();
    enclosedByCatch.pop();
    currentBlock = createBlock(currentBlock);
    currentBlock.elements.add(tryStatementTree);
    if (finallyBlockTree != null) {
      exitBlocks.pop();
      continueTargets.removeLast();
      breakTargets.removeLast();
    }
  }

  private void buildThrowStatement(ThrowStatementTree throwStatementTree) {
    Block jumpTo = exitBlock();
    TryStatement enclosingTryCatch = enclosingTry.peek();
    if(enclosingTryCatch != null){
      jumpTo = enclosingTryCatch.catches.keySet().stream()
        .filter(t -> checkTypeSemantic(throwStatementTree.expression().symbolType(), "thrown expression", throwStatementTree.expression().firstToken()).isSubtypeOf(t))
        .findFirst()
        .map(t -> enclosingTryCatch.catches.get(t))
        .orElse(exitBlock());
    }
    currentBlock = createUnconditionalJump(throwStatementTree, jumpTo, currentBlock);
    build(throwStatementTree.expression());
  }

  private void buildSynchronizedStatement(SynchronizedStatementTree sst) {
    // First create the block of the statement,
    build(sst.block());
    // Then create a single block with the SYNCHRONIZED tree as terminator
    currentBlock = createUnconditionalJump(sst, currentBlock, null);
    build(sst.expression());
  }

  private void buildUnaryExpression(UnaryExpressionTree tree) {
    currentBlock.elements.add(tree);
    build(tree.expression());
  }

  private void buildArrayAccessExpression(ArrayAccessExpressionTree tree) {
    currentBlock.elements.add(tree);
    build(tree.dimension());
    build(tree.expression());
  }

  private void buildArrayDimension(ArrayDimensionTree tree) {
    ExpressionTree expression = tree.expression();
    if (expression != null) {
      build(expression);
    }
  }

  private void buildNewClass(NewClassTree tree) {
    SyntaxToken location = tree.identifier().lastToken();
    checkSymbolSemantic(tree.methodSymbol(), "new class", location);
    handleExceptionalPaths(tree.methodSymbol(), location);
    currentBlock.elements.add(tree);
    build(tree.arguments());
    ExpressionTree enclosingExpression = tree.enclosingExpression();
    if (enclosingExpression != null) {
      build(enclosingExpression);
    }
  }

  private void handleExceptionalPaths(Symbol.MethodSymbol symbol, @Nullable SyntaxToken location) {
    TryStatement pop = enclosingTry.pop();
    TryStatement tryStatement;
    Block exceptionPredecessor = currentBlock;
    if (Boolean.TRUE.equals(enclosedByCatch.peek())) {
      tryStatement = enclosingTry.peek();
    } else {
      tryStatement = pop;
    }
    enclosingTry.push(pop);
    if(pop != outerTry) {
      currentBlock = createBlock(currentBlock);
      currentBlock.exceptions.add(exitBlocks.peek());
      if (!Boolean.TRUE.equals(enclosedByCatch.peek())) {
        exceptionPredecessor = currentBlock;
      }
    }
    if (!symbol.isUnknown()) {
      List thrownTypes = symbol.thrownTypes();
      thrownTypes.forEach(thrownType -> {
        checkTypeSemantic(thrownType, "thrown types", location);
        for (Type caughtType : tryStatement.catches.keySet()) {
          if (thrownType.isSubtypeOf(caughtType) ||
            caughtType.isSubtypeOf(thrownType) ||
            thrownType.isUnknown() ||
            // note that this condition is not necessary, because unknown type will be added to runtimeCatches due to condition in
            // org.sonar.java.cfg.CFG.TryStatement#addCatch ,however, it is here for clarity
            caughtType.isUnknown()) {
            currentBlock.exceptions.add(tryStatement.catches.get(caughtType));
          }
        }
      });
    }
    exceptionPredecessor.exceptions.addAll(tryStatement.runtimeCatches);
  }

  private void buildTypeCast(Tree tree) {
    enclosingTry.peek().catches.entrySet().stream()
      .filter(e -> e.getKey().isSubtypeOf("java.lang.ClassCastException"))
      .findFirst()
      .ifPresent(e -> {
        currentBlock = createBlock(currentBlock);
        currentBlock.successors.add(e.getValue());
    });
    currentBlock.elements.add(tree);
    TypeCastTree typeCastTree = (TypeCastTree) tree;
    build(typeCastTree.expression());
  }

  private void buildInstanceOf(InstanceOfTree instanceOfTree) {
    currentBlock.elements.add(instanceOfTree);
    build(instanceOfTree.expression());
  }

  private void buildInstanceOf(PatternInstanceOfTree instanceOfTree) {
    currentBlock.elements.add(instanceOfTree);
    build(instanceOfTree.pattern());
    build(instanceOfTree.expression());
  }

  private void buildNewArray(NewArrayTree tree) {
    currentBlock.elements.add(tree);
    build(tree.dimensions());
    build(tree.initializers());
  }

  private void buildAssertStatement(AssertStatementTree assertStatementTree) {
    currentBlock.elements.add(assertStatementTree);
    // Ignore detail expression as it is only evaluated when assertion is false.
    build(assertStatementTree.condition());
  }

  private Block createUnconditionalJump(Tree terminator, @Nullable Block target, @Nullable Block successorWithoutJump) {
    Block result = createBlock();
    result.terminator = terminator;
    if (target != null) {
      if (target == exitBlock()) {
        result.addExitSuccessor(target);
      } else {
        result.addSuccessor(target);
      }
    }
    result.successorWithoutJump = successorWithoutJump;
    return result;
  }

  private void buildCondition(Tree syntaxNode, Block trueBlock, Block falseBlock) {
    switch (syntaxNode.kind()) {
      case CONDITIONAL_OR:
        buildConditionalOr((BinaryExpressionTree) syntaxNode, trueBlock, falseBlock);
        break;
      case CONDITIONAL_AND:
        // process RHS
        buildConditionalAnd((BinaryExpressionTree) syntaxNode, trueBlock, falseBlock);
        break;
      // Skip syntactic sugar:
      case PARENTHESIZED_EXPRESSION:
        buildCondition(((ParenthesizedTree) syntaxNode).expression(), trueBlock, falseBlock);
        break;
      default:
        build(syntaxNode);
        break;
    }
  }

  private void buildConditionalOr(BinaryExpressionTree conditionalOr, Block trueBlock, Block falseBlock) {
    // process RHS
    buildCondition(conditionalOr.rightOperand(), trueBlock, falseBlock);
    Block newFalseBlock = currentBlock;
    // process LHS
    currentBlock = createBranch(conditionalOr, trueBlock, newFalseBlock);
    buildCondition(conditionalOr.leftOperand(), trueBlock, newFalseBlock);
  }

  private void buildConditionalAnd(BinaryExpressionTree conditionalAnd, Block trueBlock, Block falseBlock) {
    buildCondition(conditionalAnd.rightOperand(), trueBlock, falseBlock);
    Block newTrueBlock = currentBlock;
    // process LHS
    currentBlock = createBranch(conditionalAnd, newTrueBlock, falseBlock);
    buildCondition(conditionalAnd.leftOperand(), newTrueBlock, falseBlock);
  }

  private Block createBranch(Tree terminator, Block trueBranch, Block falseBranch) {
    Block result = createBlock();
    result.terminator = terminator;
    result.addFalseSuccessor(falseBranch);
    result.addTrueSuccessor(trueBranch);
    return result;
  }

  public void setMethodSymbol(Symbol.MethodSymbol methodSymbol) {
    this.methodSymbol = methodSymbol;
  }

  private void buildPattern(PatternTree tree) {
    switch (tree.kind()) {
      case NULL_PATTERN:
        buildNullPattern((NullPatternTree) tree);
        break;
      case TYPE_PATTERN:
        buildTypePattern((TypePatternTree) tree);
        break;
      case GUARDED_PATTERN:
        buildGuardedPattern((GuardedPatternTree) tree);
        break;
      case RECORD_PATTERN:
        buildRecordPattern((RecordPatternTree) tree);
        break;
      case DEFAULT_PATTERN:
        // do nothing - handled when building the switch
        currentBlock.elements.add(tree);
        break;
      default:
        throw new UnsupportedOperationException(tree.kind().name() + " " + ((JavaTree) tree).getLine());
    }
  }

  private void buildNullPattern(NullPatternTree tree) {
    currentBlock.elements.add(tree.nullLiteral());
    currentBlock.elements.add(tree);
  }

  private void buildTypePattern(TypePatternTree tree) {
    buildVariable(tree.patternVariable());
    currentBlock.elements.add(tree);
  }

  private void buildGuardedPattern(GuardedPatternTree tree) {
    // reverted order
    build(tree.expression());
    build(tree.pattern());
    currentBlock.elements.add(tree);
  }

  private void buildRecordPattern(RecordPatternTree tree) {
    build(tree.patterns());
    build(tree.type());
    currentBlock.elements.add(tree);
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy