
org.sonar.java.cfg.LiveVariables Maven / Gradle / Ivy
/*
* 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.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree;
import org.sonar.plugins.java.api.tree.MethodReferenceTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.Tree.Kind;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonarsource.analyzer.commons.collections.ListUtils;
import org.sonarsource.analyzer.commons.collections.SetUtils;
public class LiveVariables {
private final CFG cfg;
private final Map> out = new HashMap<>();
private final Map> in = new HashMap<>();
private final boolean includeFields;
private LiveVariables(CFG cfg, boolean includeFields) {
this.cfg = cfg;
this.includeFields = includeFields;
}
public Set getOut(CFG.Block block) {
return out.get(block);
}
public Set getIn(CFG.Block block) {
return in.get(block);
}
/**
* Returns LiveVariables object with information concerning local variables and parameters
*/
public static LiveVariables analyze(CFG cfg) {
return analyze(cfg, false);
}
/**
* Returns LiveVariables object with information concerning local variables, parameters and fields
*/
public static LiveVariables analyzeWithFields(CFG cfg) {
return analyze(cfg, true);
}
private static LiveVariables analyze(CFG cfg, boolean includeFields) {
LiveVariables liveVariables = new LiveVariables(cfg, includeFields);
// Generate kill/gen for each block in isolation
Map> kill = new HashMap<>();
Map> gen = new HashMap<>();
for (CFG.Block block : liveVariables.cfg.reversedBlocks()) {
Set blockKill = new HashSet<>();
Set blockGen = new HashSet<>();
liveVariables.processBlockElements(block, blockKill, blockGen);
kill.put(block, blockKill);
gen.put(block, blockGen);
}
liveVariables.analyzeCFG(liveVariables.in, kill, gen);
// out of exit block are empty by definition.
if (!liveVariables.out.get(liveVariables.cfg.reversedBlocks().get(0)).isEmpty()) {
throw new IllegalStateException("Out of exit block should be empty");
}
// Make things immutable.
for (Map.Entry> blockSetEntry : liveVariables.out.entrySet()) {
blockSetEntry.setValue(Collections.unmodifiableSet(blockSetEntry.getValue()));
}
return liveVariables;
}
private void analyzeCFG(Map> in, Map> kill, Map> gen) {
Deque workList = new LinkedList<>();
workList.addAll(cfg.reversedBlocks());
while (!workList.isEmpty()) {
CFG.Block block = workList.removeFirst();
Set blockOut = out.computeIfAbsent(block, k -> new HashSet<>());
block.successors().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll);
block.exceptions().stream().map(in::get).filter(Objects::nonNull).forEach(blockOut::addAll);
// in = gen and (out - kill)
Set newIn = new HashSet<>(gen.get(block));
newIn.addAll(SetUtils.difference(blockOut, kill.get(block)));
if (newIn.equals(in.get(block))) {
continue;
}
in.put(block, newIn);
block.predecessors().forEach(workList::addLast);
}
}
private void processBlockElements(CFG.Block block, Set blockKill, Set blockGen) {
// process elements from bottom to top
Set assignmentLHS = new HashSet<>();
for (Tree element : ListUtils.reverse(block.elements())) {
switch (element.kind()) {
case ASSIGNMENT:
processAssignment((AssignmentExpressionTree) element, blockKill, blockGen, assignmentLHS);
break;
case IDENTIFIER:
processIdentifier((IdentifierTree) element, blockGen, assignmentLHS);
break;
case MEMBER_SELECT:
processMemberSelect((MemberSelectExpressionTree) element, assignmentLHS, blockGen);
break;
case VARIABLE:
blockKill.add(((VariableTree) element).symbol());
blockGen.remove(((VariableTree) element).symbol());
break;
case LAMBDA_EXPRESSION:
blockGen.addAll(getUsedVariables(((LambdaExpressionTree) element).body(), cfg.methodSymbol()));
break;
case METHOD_REFERENCE:
blockGen.addAll(getUsedVariables(((MethodReferenceTree) element).expression(), cfg.methodSymbol()));
break;
case NEW_CLASS:
blockGen.addAll(getUsedVariables(((NewClassTree) element).classBody(), cfg.methodSymbol()));
break;
default:
// Ignore other kind of elements, no change of gen/kill
}
}
}
private void processIdentifier(IdentifierTree element, Set blockGen, Set assignmentLHS) {
Symbol symbol = element.symbol();
if (!assignmentLHS.contains(element) && includeSymbol(symbol)) {
blockGen.add(symbol);
}
}
private void processMemberSelect(MemberSelectExpressionTree element, Set assignmentLHS, Set blockGen) {
Symbol symbol;
if (!assignmentLHS.contains(element) && includeFields) {
symbol = getField(element);
if (symbol != null) {
blockGen.add(symbol);
}
}
}
private void processAssignment(AssignmentExpressionTree element, Set blockKill, Set blockGen, Set assignmentLHS) {
Symbol symbol = null;
ExpressionTree lhs = element.variable();
if (lhs.is(Kind.IDENTIFIER)) {
symbol = ((IdentifierTree) lhs).symbol();
} else if (includeFields && lhs.is(Kind.MEMBER_SELECT)) {
symbol = getField((MemberSelectExpressionTree) lhs);
}
if (symbol != null && includeSymbol(symbol)) {
assignmentLHS.add(lhs);
blockGen.remove(symbol);
blockKill.add(symbol);
}
}
private boolean includeSymbol(Symbol symbol) {
return symbol.isLocalVariable() || (includeFields && isField(symbol));
}
private static boolean isField(Symbol symbol) {
return symbol.owner().isTypeSymbol() && !"this".equals(symbol.name()) && symbol.isVariableSymbol();
}
@CheckForNull
private static Symbol getField(MemberSelectExpressionTree memberSelect) {
Symbol symbol = memberSelect.identifier().symbol();
if (memberSelect.expression().is(Kind.IDENTIFIER)) {
String objectName = ((IdentifierTree) memberSelect.expression()).name();
if (symbol.isStatic() || "this".equals(objectName)) {
return symbol;
}
} else if (symbol.isStatic()) {
return symbol;
}
return null;
}
private Set getUsedVariables(@Nullable Tree syntaxNode, Symbol.MethodSymbol owner) {
if(syntaxNode == null) {
return Collections.emptySet();
}
VariableReadExtractor extractorFromClass = new VariableReadExtractor(owner, includeFields);
syntaxNode.accept(extractorFromClass);
return extractorFromClass.usedVariables();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy