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

org.sonar.python.cfg.fixpoint.ReachingDefinitionsAnalysis 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.fixpoint;

import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.sonar.plugins.python.api.PythonFile;
import org.sonar.plugins.python.api.cfg.CfgBlock;
import org.sonar.plugins.python.api.cfg.ControlFlowGraph;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.plugins.python.api.tree.AnnotatedAssignment;
import org.sonar.plugins.python.api.tree.AssignmentStatement;
import org.sonar.plugins.python.api.tree.BaseTreeVisitor;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.tree.TreeUtils;

import static org.sonar.plugins.python.api.tree.Tree.Kind.ASSIGNMENT_STMT;
import static org.sonar.plugins.python.api.tree.Tree.Kind.FUNCDEF;
import static org.sonar.plugins.python.api.tree.Tree.Kind.TRY_STMT;

/**
 * https://en.wikipedia.org/wiki/Reaching_definition
 * Data flow analysis to determinate what definitions may reach a given point in the code.
 * Program state is represented by a map where:
 *  - key is symbol
 *  - value is set of possible expressions that symbol may have been assigned to
 */
public class ReachingDefinitionsAnalysis {
  private final Map programStateByBlock = new HashMap<>();
  private final Map> assignedExpressionByName = new HashMap<>();
  private final PythonFile pythonFile;
  private final Map> assignedNamesBySymbol = new HashMap<>();

  public ReachingDefinitionsAnalysis(PythonFile pythonFile) {
    this.pythonFile = pythonFile;
  }

  public Set valuesAtLocation(Name variable) {
    Symbol symbol = variable.symbol();
    if (symbol == null) {
      return Collections.emptySet();
    }
    Set assignedExpressions = assignedExpressionByName.get(variable);
    if (assignedExpressions != null) {
      // avoid recomputing cfg and analysis
      return assignedExpressions;
    }
    FunctionDef enclosingFunction = (FunctionDef) TreeUtils.firstAncestorOfKind(variable, FUNCDEF);
    if (enclosingFunction == null || TreeUtils.hasDescendant(enclosingFunction, t -> t.is(TRY_STMT))) {
      return Collections.emptySet();
    }
    ControlFlowGraph cfg = ControlFlowGraph.build(enclosingFunction, pythonFile);
    if (cfg == null) {
      return Collections.emptySet();
    }
    compute(cfg, enclosingFunction.localVariables());
    return assignedExpressionByName.getOrDefault(variable, Collections.emptySet());
  }

  private Set getAssignedExpressions(Name variable, ProgramStateAtElement programStateAtElement) {
    Symbol symbol = variable.symbol();
    if (symbol == null) {
      return Collections.emptySet();
    }
    boolean hasMissingBindingUsage = symbol.usages().stream()
        .filter(Usage::isBindingUsage)
        .anyMatch(u -> !assignedNamesBySymbol.getOrDefault(symbol, Collections.emptySet()).contains(u.tree()));
    if (hasMissingBindingUsage) {
      return Collections.emptySet();
    }
    return Optional.ofNullable(programStateAtElement.out.get(symbol))
        .orElse(Collections.emptySet());
  }

  private void compute(ControlFlowGraph cfg, Set localVariables) {
    Map> initialState = new HashMap<>();
    for (Symbol variable : localVariables) {
      initialState.put(variable, new HashSet<>());
    }
    Set blocks = cfg.blocks();
    blocks.forEach(block -> programStateByBlock.put(block, new ProgramStateAtBlock(block, initialState)));
    Deque workList = new ArrayDeque<>(blocks);
    while (!workList.isEmpty()) {
      CfgBlock currentBlock = workList.pop();
      ProgramStateAtBlock programStateAtBlock = programStateByBlock.get(currentBlock);
      boolean outHasChanged = programStateAtBlock.propagate();
      if (outHasChanged) {
        currentBlock.successors().forEach(workList::push);
      }
    }
    updateProgramStateByElement(cfg);
  }

  private void updateProgramStateByElement(ControlFlowGraph cfg) {
    for (CfgBlock block : cfg.blocks()) {
      Map> inputState = programStateByBlock.get(block).in;
      for (Tree element : block.elements()) {
        ProgramStateAtElement programStateAtElement = new ProgramStateAtElement(inputState, element);
        element.accept(new BaseTreeVisitor() {
          @Override
          public void visitFunctionDef(FunctionDef pyFunctionDefTree) {
            // skip inner functions
          }

          @Override
          public void visitName(Name name) {
            assignedExpressionByName.put(name, getAssignedExpressions(name, programStateAtElement));
          }
        });
        inputState = programStateAtElement.out;
      }
    }
  }

  private class ProgramStateAtBlock {

    private final CfgBlock block;
    private Map> in;
    private Map> out = new HashMap<>();

    private ProgramStateAtBlock(CfgBlock block, Map> initialState) {
      this.block = block;
      this.in = initialState;
      this.block.elements().forEach(element -> updateProgramState(element, out));
    }

    /**
     * Propagates forward: first computes the in set from all predecessors, then the out set.
     */
    private boolean propagate() {
      block.predecessors().forEach(predecessor -> in = join(in, programStateByBlock.get(predecessor).out));
      Map> newOut = new HashMap<>(in);
      block.elements().forEach(element -> updateProgramState(element, newOut));
      boolean outHasChanged = !newOut.equals(out);
      out = newOut;
      return outHasChanged;
    }
  }

  private class ProgramStateAtElement {
    private final Map> out;

    public ProgramStateAtElement(Map> in, Tree element) {
      out = join(in, Collections.emptyMap());
      updateProgramState(element, out);
    }
  }

  private static Map> join(Map> programState1,
      Map> programState2) {
    Map> result = new HashMap<>();
    Set allKeys = new HashSet<>(programState1.keySet());
    allKeys.addAll(programState2.keySet());
    for (Symbol key : allKeys) {
      Set union = new HashSet<>();
      union.addAll(programState1.getOrDefault(key, Collections.emptySet()));
      union.addAll(programState2.getOrDefault(key, Collections.emptySet()));
      result.put(key, union);
    }
    return result;
  }

  private void updateProgramState(Tree element, Map> out) {
    if (element.is(ASSIGNMENT_STMT)) {
      updateStateForAssignment((AssignmentStatement) element, out);
    }
    if (element.is(Tree.Kind.ANNOTATED_ASSIGNMENT)) {
      updateStateForAnnotatedAssignement((AnnotatedAssignment) element, out);
    }
  }

  private void updateStateForAssignment(AssignmentStatement element, Map> programState) {
    List lhsExpressions = element.lhsExpressions().stream()
        .flatMap(exprList -> exprList.expressions().stream())
        .toList();
    performUpdate(programState, lhsExpressions, element::assignedValue);
  }

  private void updateStateForAnnotatedAssignement(AnnotatedAssignment element,
      Map> programState) {
    List lhsExpressions = List.of(element.variable());
    performUpdate(programState, lhsExpressions, element::assignedValue);
  }

  private void performUpdate(Map> programState, List lhsExpressions,
      Supplier assignedValueSupplier) {
    getLhsExpression(lhsExpressions)
        .ifPresent(name -> TreeUtils.getSymbolFromTree(name)
            .ifPresent(symbol -> performUpdate(symbol, assignedValueSupplier, name, programState)));
  }

  private void performUpdate(Symbol symbol, Supplier assignedValueSupplier, Expression lhsExpression,
      Map> programState) {
    assignedNamesBySymbol.computeIfAbsent(symbol, s -> new HashSet<>()).add(((Name) lhsExpression));
    Set assignedValues = new HashSet<>();
    var assignedValue = assignedValueSupplier.get();
    if (assignedValue != null) {
      assignedValues.add(assignedValue);
      // performing a strong update
      programState.put(symbol, assignedValues);
    }
  }

  private static Optional getLhsExpression(List lhsExpressions) {
    return Stream.of(lhsExpressions)
        .filter(l -> l.size() == 1)
        .flatMap(Collection::stream)
        .findFirst()
        .flatMap(TreeUtils.toOptionalInstanceOfMapper(Name.class));

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy