SideEffectAnalyzer.java
/*******************************************************************************
* Copyright (c) 2025 Carsten Hammer.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Carsten Hammer
*******************************************************************************/
package org.sandbox.jdt.internal.corext.fix.helper.lib;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
/**
* Side Effect Analyzer - Analyzes whether code replacement is semantically safe
*
* This class checks for side effects and ensures that replacing an inline code
* sequence with a method call preserves the original semantics.
*/
public class SideEffectAnalyzer {
/**
* Check if a sequence of statements is safe to replace with a method call
*
* @param statements The statements to analyze
* @return true if replacement is safe
*/
public static boolean isSafeToReplace(List<Statement> statements) {
if (statements == null || statements.isEmpty()) {
return false;
}
// Check each statement for problematic patterns
for (Statement stmt : statements) {
if (!isSafeStatement(stmt)) {
return false;
}
}
return true;
}
/**
* Check if a single statement is safe
*/
private static boolean isSafeStatement(Statement stmt) {
if (stmt == null) {
return false;
}
// Check for field modifications
if (hasFieldModifications(stmt)) {
return false;
}
// Check for method calls that might have side effects
if (hasUnsafeMethodCalls(stmt)) {
return false;
}
// Check for complex control flow
if (hasComplexControlFlow(stmt)) {
return false;
}
return true;
}
/**
* Check if statement modifies fields (not local variables)
*/
private static boolean hasFieldModifications(Statement stmt) {
FieldModificationVisitor visitor = new FieldModificationVisitor();
stmt.accept(visitor);
return visitor.hasFieldModification;
}
/**
* Check if statement contains method calls that might have side effects
*
* Note: This is a conservative check. We allow known safe methods like
* String.trim(), but reject most other method calls to be safe.
*/
private static boolean hasUnsafeMethodCalls(Statement stmt) {
UnsafeMethodCallVisitor visitor = new UnsafeMethodCallVisitor();
stmt.accept(visitor);
return visitor.hasUnsafeCall;
}
/**
* Check for complex control flow (loops, try-catch, etc.)
*/
private static boolean hasComplexControlFlow(Statement stmt) {
// Simple check: reject if statement contains certain node types
int nodeType = stmt.getNodeType();
// These statement types indicate complex control flow
switch (nodeType) {
case ASTNode.FOR_STATEMENT:
case ASTNode.ENHANCED_FOR_STATEMENT:
case ASTNode.WHILE_STATEMENT:
case ASTNode.DO_STATEMENT:
case ASTNode.TRY_STATEMENT:
case ASTNode.SYNCHRONIZED_STATEMENT:
case ASTNode.SWITCH_STATEMENT:
case ASTNode.BREAK_STATEMENT:
case ASTNode.CONTINUE_STATEMENT:
return true;
default:
return false;
}
}
/**
* Visitor to detect field modifications
*/
private static class FieldModificationVisitor extends ASTVisitor {
boolean hasFieldModification = false;
@Override
public boolean visit(Assignment node) {
Expression left = node.getLeftHandSide();
if (isFieldAccess(left)) {
hasFieldModification = true;
return false;
}
return true;
}
@Override
public boolean visit(PrefixExpression node) {
// Check for ++ and -- on fields
PrefixExpression.Operator op = node.getOperator();
if (op == PrefixExpression.Operator.INCREMENT || op == PrefixExpression.Operator.DECREMENT) {
if (isFieldAccess(node.getOperand())) {
hasFieldModification = true;
return false;
}
}
return true;
}
@Override
public boolean visit(PostfixExpression node) {
// Check for ++ and -- on fields
if (isFieldAccess(node.getOperand())) {
hasFieldModification = true;
return false;
}
return true;
}
private boolean isFieldAccess(Expression expr) {
return expr instanceof FieldAccess ||
expr instanceof SuperFieldAccess ||
(expr instanceof QualifiedName && isFieldReference((QualifiedName) expr));
}
private boolean isFieldReference(QualifiedName name) {
// Simple heuristic: qualified names might be field accesses
// More sophisticated analysis would use type bindings
return true;
}
}
/**
* Visitor to detect potentially unsafe method calls
*/
private static class UnsafeMethodCallVisitor extends ASTVisitor {
boolean hasUnsafeCall = false;
@Override
public boolean visit(MethodInvocation node) {
// Check if this is a known safe method
if (!isKnownSafeMethod(node)) {
hasUnsafeCall = true;
return false;
}
return true;
}
@Override
public boolean visit(SuperMethodInvocation node) {
// Super method calls might have side effects
hasUnsafeCall = true;
return false;
}
/**
* Check if a method is known to be side-effect free
*/
private boolean isKnownSafeMethod(MethodInvocation node) {
String methodName = node.getName().getIdentifier();
// Known safe String methods
if ("trim".equals(methodName) || "length".equals(methodName) ||
"isEmpty".equals(methodName) || "toString".equals(methodName)) {
return true;
}
// For now, be conservative and reject other methods
// A more sophisticated analysis would check method purity
return false;
}
}
}