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

org.sonar.python.cfg.ControlFlowGraphBuilder Maven / Gradle / Ivy

There is a newer version: 4.26.0.19456
Show newest version
/*
 * SonarQube Python Plugin
 * Copyright (C) 2011-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.python.cfg;

import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
import org.sonar.plugins.python.api.cfg.CfgBlock;
import org.sonar.plugins.python.api.cfg.ControlFlowGraph;
import org.sonar.plugins.python.api.tree.AnyParameter;
import org.sonar.plugins.python.api.tree.BreakStatement;
import org.sonar.plugins.python.api.tree.CaseBlock;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.ContinueStatement;
import org.sonar.plugins.python.api.tree.ElseClause;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.FinallyClause;
import org.sonar.plugins.python.api.tree.ForStatement;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Guard;
import org.sonar.plugins.python.api.tree.IfStatement;
import org.sonar.plugins.python.api.tree.MatchStatement;
import org.sonar.plugins.python.api.tree.ParameterList;
import org.sonar.plugins.python.api.tree.Pattern;
import org.sonar.plugins.python.api.tree.RaiseStatement;
import org.sonar.plugins.python.api.tree.ReturnStatement;
import org.sonar.plugins.python.api.tree.Statement;
import org.sonar.plugins.python.api.tree.StatementList;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.TryStatement;
import org.sonar.plugins.python.api.tree.TupleParameter;
import org.sonar.plugins.python.api.tree.WhileStatement;
import org.sonar.plugins.python.api.tree.WithStatement;
import org.sonar.python.tree.TreeUtils;

public class ControlFlowGraphBuilder {

  private PythonCfgBlock start;
  private final PythonCfgBlock end = new PythonCfgEndBlock();
  private final Set blocks = new LinkedHashSet<>();
  private final Deque loops = new ArrayDeque<>();
  private final Deque exceptionTargets = new ArrayDeque<>();
  private final Deque exitTargets = new ArrayDeque<>();

  public ControlFlowGraphBuilder(@Nullable StatementList statementList) {
    blocks.add(end);
    exceptionTargets.push(end);
    exitTargets.push(end);
    if (statementList != null) {
      start = build(statementList.statements(), createSimpleBlock(end));
      addParametersToStartBlock(statementList);
    } else {
      start = end;
    }
    removeEmptyBlocks();
    computePredecessors();
  }

  private void addParametersToStartBlock(StatementList statementList) {
    if (statementList.parent().is(Tree.Kind.FUNCDEF)) {
      ParameterList parameterList = ((FunctionDef) statementList.parent()).parameters();
      if (parameterList != null) {
        PythonCfgSimpleBlock parametersBlock = createSimpleBlock(start);
        addParameters(parameterList.all(), parametersBlock);
        start = parametersBlock;
      }
    }
  }

  private static void addParameters(List parameters, PythonCfgSimpleBlock parametersBlock) {
    for (AnyParameter parameter : parameters) {
      if (parameter.is(Tree.Kind.TUPLE_PARAMETER)) {
        addParameters(((TupleParameter) parameter).parameters(), parametersBlock);
      } else {
        parametersBlock.addElement(parameter);
      }
    }
  }

  private void computePredecessors() {
    for (PythonCfgBlock block : blocks) {
      for (CfgBlock successor : block.successors()) {
        ((PythonCfgBlock) successor).addPredecessor(block);
      }
    }
  }

  private void removeEmptyBlocks() {
    Map emptyBlockReplacements = new HashMap<>();
    for (PythonCfgBlock block : blocks) {
      if (block.isEmptyBlock()) {
        PythonCfgBlock firstNonEmptySuccessor = block.firstNonEmptySuccessor();
        emptyBlockReplacements.put(block, firstNonEmptySuccessor);
      }
    }

    blocks.removeAll(emptyBlockReplacements.keySet());

    for (PythonCfgBlock block : blocks) {
      block.replaceSuccessors(emptyBlockReplacements);
    }

    start = emptyBlockReplacements.getOrDefault(start, start);
  }

  public ControlFlowGraph getCfg() {
    return new ControlFlowGraph(Collections.unmodifiableSet(blocks), start, end);
  }

  private PythonCfgSimpleBlock createSimpleBlock(CfgBlock successor) {
    PythonCfgSimpleBlock block = new PythonCfgSimpleBlock(successor);
    blocks.add(block);
    return block;
  }

  private PythonCfgBranchingBlock createBranchingBlock(Tree branchingTree, CfgBlock trueSuccessor, CfgBlock falseSuccessor) {
    PythonCfgBranchingBlock block = new PythonCfgBranchingBlock(branchingTree, trueSuccessor, falseSuccessor);
    blocks.add(block);
    return block;
  }

  private PythonCfgBranchingBlock createBranchingBlock(Tree branchingTree, CfgBlock falseSuccessor) {
    PythonCfgBranchingBlock block = new PythonCfgBranchingBlock(branchingTree, null, falseSuccessor);
    blocks.add(block);
    return block;
  }

  private PythonCfgBlock build(List statements, PythonCfgBlock successor) {
    PythonCfgBlock currentBlock = successor;
    for (int i = statements.size() - 1; i >= 0; i--) {
      Statement statement = statements.get(i);
      currentBlock = build(statement, currentBlock);
    }
    return currentBlock;
  }

  private PythonCfgBlock build(Statement statement, PythonCfgBlock currentBlock) {
    switch (statement.getKind()) {
      case WITH_STMT:
        return buildWithStatement((WithStatement) statement, currentBlock);
      case CLASSDEF:
        return buildClassDefStatement((ClassDef) statement, currentBlock);
      case RETURN_STMT:
        return buildReturnStatement((ReturnStatement) statement, currentBlock);
      case RAISE_STMT:
        return buildRaiseStatement((RaiseStatement) statement, currentBlock);
      case IF_STMT:
        return buildIfStatement(((IfStatement) statement), currentBlock);
      case WHILE_STMT:
        return buildWhileStatement(((WhileStatement) statement), currentBlock);
      case FOR_STMT:
        return buildForStatement(((ForStatement) statement), currentBlock);
      case CONTINUE_STMT:
        return buildContinueStatement(((ContinueStatement) statement), currentBlock);
      case TRY_STMT:
        return tryStatement(((TryStatement) statement), currentBlock);
      case BREAK_STMT:
        return buildBreakStatement((BreakStatement) statement, currentBlock);
      case MATCH_STMT:
        return buildMatchStatement((MatchStatement) statement, currentBlock);
      case FUNCDEF:
        return buildFuncDefStatement((FunctionDef) statement, currentBlock);
      default:
        currentBlock.addElement(statement);
    }

    return currentBlock;
  }

  private PythonCfgBlock buildClassDefStatement(ClassDef classDef, PythonCfgBlock currentBlock) {
    PythonCfgBlock block = build(classDef.body().statements(), currentBlock);
    block.addElement(classDef);
    classDef.decorators().stream().forEach(currentBlock::addElement);
    return block;
  }

  private static PythonCfgBlock buildFuncDefStatement(FunctionDef functionDef, PythonCfgBlock currentBlock) {
    currentBlock.addElement(functionDef);
    functionDef.decorators().stream().forEach(currentBlock::addElement);
    return currentBlock;
  }

  private PythonCfgBlock buildMatchStatement(MatchStatement statement, PythonCfgBlock successor) {
    List caseBlocks = statement.caseBlocks();
    PythonCfgBlock matchingBlock = null;
    PythonCfgBlock falseSuccessor = successor;
    for (int i = caseBlocks.size() - 1; i >= 0; i--) {
      PythonCfgBlock caseBodyBlock = createSimpleBlock(successor);
      CaseBlock caseBlock = caseBlocks.get(i);
      Pattern pattern = caseBlock.pattern();
      Guard guard = caseBlock.guard();
      caseBodyBlock = build(caseBlock.body().statements(), caseBodyBlock);
      matchingBlock = createBranchingBlock(pattern, caseBodyBlock, falseSuccessor);
      if (guard != null) {
        matchingBlock.addElement(guard.condition());
      }
      matchingBlock.addElement(pattern);
      matchingBlock.addElement(statement.subjectExpression());
      blocks.add(matchingBlock);
      falseSuccessor = matchingBlock;
    }
    return matchingBlock;
  }

  private PythonCfgBlock buildWithStatement(WithStatement withStatement, PythonCfgBlock successor) {
    PythonCfgBlock withBodyBlock = build(withStatement.statements().statements(), createSimpleBlock(successor));
    // exceptions may be raised inside with block and be caught by context manager
    // see https://docs.python.org/3/reference/compound_stmts.html#the-with-statement
    PythonCfgBranchingBlock branchingBlock = createBranchingBlock(withStatement, withBodyBlock, successor);
    for (int i = withStatement.withItems().size() - 1; i >= 0; i--) {
      branchingBlock.addElement(withStatement.withItems().get(i));
    }
    return branchingBlock;
  }

  private PythonCfgBlock tryStatement(TryStatement tryStatement, PythonCfgBlock successor) {
    PythonCfgBlock finallyOrAfterTryBlock = successor;
    FinallyClause finallyClause = tryStatement.finallyClause();
    PythonCfgBlock finallyBlock = null;
    if (finallyClause != null) {
      finallyOrAfterTryBlock = build(finallyClause.body().statements(), createBranchingBlock(finallyClause, successor, exitTargets.peek()));
      finallyBlock = finallyOrAfterTryBlock;
      exitTargets.push(finallyBlock);
      loops.push(new Loop(finallyBlock, finallyBlock));
    }
    PythonCfgBlock firstExceptClauseBlock = exceptClauses(tryStatement, finallyOrAfterTryBlock, finallyBlock);
    ElseClause elseClause = tryStatement.elseClause();
    PythonCfgBlock tryBlockSuccessor = finallyOrAfterTryBlock;
    if (elseClause != null) {
      tryBlockSuccessor = build(elseClause.body().statements(), createSimpleBlock(finallyOrAfterTryBlock));
    }
    if (finallyClause != null) {
      exitTargets.pop();
      loops.pop();
    }
    exceptionTargets.push(firstExceptClauseBlock);
    exitTargets.push(firstExceptClauseBlock);
    loops.push(new Loop(firstExceptClauseBlock, firstExceptClauseBlock));
    PythonCfgBlock firstTryBlock = build(tryStatement.body().statements(), createBranchingBlock(tryStatement, tryBlockSuccessor, firstExceptClauseBlock));
    exceptionTargets.pop();
    exitTargets.pop();
    loops.pop();
    return createSimpleBlock(firstTryBlock);
  }

  private PythonCfgBlock exceptClauses(TryStatement tryStatement, PythonCfgBlock finallyOrAfterTryBlock, @Nullable PythonCfgBlock finallyBlock) {
    PythonCfgBlock falseSuccessor = finallyBlock == null ? exceptionTargets.peek() : finallyBlock;
    List exceptClauses = tryStatement.exceptClauses();
    for (int i = exceptClauses.size() - 1; i >= 0; i--) {
      ExceptClause exceptClause = exceptClauses.get(i);
      PythonCfgBlock exceptBlock = build(exceptClause.body().statements(), createSimpleBlock(finallyOrAfterTryBlock));
      PythonCfgBlock exceptCondition = createBranchingBlock(exceptClause, exceptBlock, falseSuccessor);
      Expression exceptionInstance = exceptClause.exceptionInstance();
      if (exceptionInstance != null) {
        exceptCondition.addElement(exceptionInstance);
      }
      Expression exception = exceptClause.exception();
      if (exception != null) {
        exceptCondition.addElement(exception);
      }
      falseSuccessor = exceptCondition;
    }
    return falseSuccessor;
  }

  private Loop currentLoop(Tree tree) {
    Loop loop = loops.peek();
    if (loop == null) {
      Token token = tree.firstToken();
      throw new IllegalStateException("Invalid \"" + token.value() + "\" outside loop at line " + token.line());
    }
    return loop;
  }

  private PythonCfgBlock buildBreakStatement(BreakStatement breakStatement, PythonCfgBlock syntacticSuccessor) {
    PythonCfgSimpleBlock block = createSimpleBlock(currentLoop(breakStatement).breakTarget);
    block.setSyntacticSuccessor(syntacticSuccessor);
    block.addElement(breakStatement);
    return block;
  }

  private PythonCfgBlock buildContinueStatement(ContinueStatement continueStatement, PythonCfgBlock syntacticSuccessor) {
    PythonCfgSimpleBlock block = createSimpleBlock(currentLoop(continueStatement).continueTarget);
    block.setSyntacticSuccessor(syntacticSuccessor);
    block.addElement(continueStatement);
    return block;
  }

  private PythonCfgBlock buildLoop(Tree branchingTree, List conditionElements, StatementList body, @Nullable ElseClause elseClause, PythonCfgBlock successor) {
    PythonCfgBlock afterLoopBlock = successor;
    if (elseClause != null) {
      afterLoopBlock = build(elseClause.body().statements(), createSimpleBlock(successor));
    }
    PythonCfgBranchingBlock conditionBlock = createBranchingBlock(branchingTree, afterLoopBlock);
    conditionElements.forEach(conditionBlock::addElement);
    loops.push(new Loop(successor, conditionBlock));
    PythonCfgBlock loopBodyBlock = build(body.statements(), createSimpleBlock(conditionBlock));
    loops.pop();
    conditionBlock.setTrueSuccessor(loopBodyBlock);
    return createSimpleBlock(conditionBlock);
  }

  private PythonCfgBlock buildForStatement(ForStatement forStatement, PythonCfgBlock successor) {
    PythonCfgBlock beforeForStmt = buildLoop(forStatement, forStatement.expressions(), forStatement.body(), forStatement.elseClause(), successor);
    forStatement.testExpressions().forEach(beforeForStmt::addElement);
    return beforeForStmt;
  }

  private PythonCfgBlock buildWhileStatement(WhileStatement whileStatement, PythonCfgBlock currentBlock) {
    return buildLoop(whileStatement, Collections.singletonList(whileStatement.condition()), whileStatement.body(), whileStatement.elseClause(), currentBlock);
  }

  /**
   * CFG for if-elif-else statement:
   *
   *                +-----------+
   *       +--------+ before_if +-------+
   *       |        +-----------+       |
   *       |                            |
   * +-----v----+                +------v-----+
   * | if_body  |          +-----+ elif_cond  +-----+
   * +----+-----+          |     +------------+     |
   *      |                |                        |
   *      |          +-----v-----+            +-----v-----+
   *      |          | elif_body |            | else_body |
   *      |          +-----+-----+            +-----+-----+
   *      |                |                        |
   *      |        +-------v-----+                  |
   *      +-------->  after_if   <------------------+
   *               +-------------+
   */
  private PythonCfgBlock buildIfStatement(IfStatement ifStatement, PythonCfgBlock afterBlock) {
    PythonCfgBlock ifBodyBlock = createSimpleBlock(afterBlock);
    ifBodyBlock = build(ifStatement.body().statements(), ifBodyBlock);
    ElseClause elseClause = ifStatement.elseBranch();
    PythonCfgBlock falseSuccessor = afterBlock;
    if (elseClause != null) {
      PythonCfgBlock elseBodyBlock = createSimpleBlock(afterBlock);
      elseBodyBlock = build(elseClause.body().statements(), elseBodyBlock);
      falseSuccessor = elseBodyBlock;
    }
    falseSuccessor = buildElifClauses(afterBlock, falseSuccessor, ifStatement.elifBranches());
    PythonCfgBlock beforeIfBlock = createBranchingBlock(ifStatement, ifBodyBlock, falseSuccessor);
    beforeIfBlock.addElement(ifStatement.condition());
    return beforeIfBlock;
  }

  private PythonCfgBlock buildElifClauses(PythonCfgBlock currentBlock, PythonCfgBlock falseSuccessor, List elifBranches) {
    for (int i = elifBranches.size() - 1; i >= 0; i--) {
      IfStatement elifStatement = elifBranches.get(i);
      PythonCfgBlock elifBodyBlock = createSimpleBlock(currentBlock);
      elifBodyBlock = build(elifStatement.body().statements(), elifBodyBlock);
      PythonCfgBlock beforeElifBlock = createBranchingBlock(elifStatement, elifBodyBlock, falseSuccessor);
      beforeElifBlock.addElement(elifStatement.condition());
      falseSuccessor = beforeElifBlock;
    }
    return falseSuccessor;
  }

  private PythonCfgBlock buildReturnStatement(ReturnStatement statement, PythonCfgBlock syntacticSuccessor) {
    if (TreeUtils.firstAncestorOfKind(statement, Tree.Kind.FUNCDEF) == null || isStatementAtClassLevel(statement)) {
      throw new IllegalStateException("Invalid return outside of a function");
    }
    PythonCfgSimpleBlock block = createSimpleBlock(exitTargets.peek());
    block.setSyntacticSuccessor(syntacticSuccessor);
    block.addElement(statement);
    return block;
  }

  // assumption: parent of return statement is always a statementList, which, in turn, has always a parent
  private static boolean isStatementAtClassLevel(ReturnStatement statement) {
    return statement.parent().parent().is(Tree.Kind.CLASSDEF);
  }

  private PythonCfgBlock buildRaiseStatement(RaiseStatement statement, PythonCfgBlock syntacticSuccessor) {
    PythonCfgSimpleBlock block = createSimpleBlock(exceptionTargets.peek());
    block.setSyntacticSuccessor(syntacticSuccessor);
    block.addElement(statement);
    return block;
  }

  private static class Loop {

    final PythonCfgBlock breakTarget;
    final PythonCfgBlock continueTarget;

    private Loop(PythonCfgBlock breakTarget, PythonCfgBlock continueTarget) {
      this.breakTarget = breakTarget;
      this.continueTarget = continueTarget;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy