/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.sunsecure;
import java.util.ArrayList;
import java.util.List;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAssignmentOperator;
import net.sourceforge.pmd.lang.java.ast.ASTBlockStatement;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTConstructorDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTEqualityExpression;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameter;
import net.sourceforge.pmd.lang.java.ast.ASTFormalParameters;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryExpression;
import net.sourceforge.pmd.lang.java.ast.ASTPrimaryPrefix;
import net.sourceforge.pmd.lang.java.ast.ASTPrimarySuffix;
import net.sourceforge.pmd.lang.java.ast.ASTStatementExpression;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
/**
* If a method or constructor receives an array as an argument, the array should
* be cloned instead of directly stored. This prevents future changes from the
* user from affecting the original array.
*
* @since Created on Jan 17, 2005
* @author mgriffa
*/
public class ArrayIsStoredDirectlyRule extends AbstractSunSecureRule {
@Override
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isInterface()) {
return data;
}
return super.visit(node, data);
}
@Override
public Object visit(ASTConstructorDeclaration node, Object data) {
ASTFormalParameter[] arrs = getArrays(node.getParameters());
if (arrs != null) {
// TODO check if one of these arrays is stored in a non local
// variable
List<ASTBlockStatement> bs = node.findDescendantsOfType(ASTBlockStatement.class);
checkAll(data, arrs, bs);
}
return data;
}
@Override
public Object visit(ASTMethodDeclaration node, Object data) {
final ASTFormalParameters params = node.getFirstDescendantOfType(ASTFormalParameters.class);
ASTFormalParameter[] arrs = getArrays(params);
if (arrs != null) {
checkAll(data, arrs, node.findDescendantsOfType(ASTBlockStatement.class));
}
return data;
}
private void checkAll(Object context, ASTFormalParameter[] arrs, List<ASTBlockStatement> bs) {
for (ASTFormalParameter element : arrs) {
checkForDirectAssignment(context, element, bs);
}
}
private String getExpressionVarName(Node e) {
String assignedVar = getFirstNameImage(e);
if (assignedVar == null) {
ASTPrimarySuffix suffix = e.getFirstDescendantOfType(ASTPrimarySuffix.class);
if (suffix != null) {
assignedVar = suffix.getImage();
ASTPrimaryPrefix prefix = e.getFirstDescendantOfType(ASTPrimaryPrefix.class);
if (prefix != null) {
if (prefix.usesThisModifier()) {
assignedVar = "this." + assignedVar;
} else if (prefix.usesSuperModifier()) {
assignedVar = "super." + assignedVar;
}
}
}
}
return assignedVar;
}
/**
* Checks if the variable designed in parameter is written to a field (not
* local variable) in the statements.
*/
private boolean checkForDirectAssignment(Object ctx, final ASTFormalParameter parameter,
final List<ASTBlockStatement> bs) {
final ASTVariableDeclaratorId vid = parameter.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
final String varName = vid.getImage();
for (ASTBlockStatement b : bs) {
if (b.hasDescendantOfType(ASTAssignmentOperator.class)) {
final ASTStatementExpression se = b.getFirstDescendantOfType(ASTStatementExpression.class);
if (se == null || !(se.jjtGetChild(0) instanceof ASTPrimaryExpression)) {
continue;
}
String assignedVar = getExpressionVarName(se);
if (assignedVar == null) {
continue;
}
ASTPrimaryExpression pe = (ASTPrimaryExpression) se.jjtGetChild(0);
Node n = pe.getFirstParentOfType(ASTMethodDeclaration.class);
if (n == null) {
n = pe.getFirstParentOfType(ASTConstructorDeclaration.class);
if (n == null) {
continue;
}
}
if (!isLocalVariable(assignedVar, n)) {
// TODO could this be more clumsy? We really
// need to build out the PMD internal framework more
// to support simply queries like "isAssignedTo()" or
// something
if (se.jjtGetNumChildren() < 3) {
continue;
}
ASTExpression e = (ASTExpression) se.jjtGetChild(2);
if (e.hasDescendantOfType(ASTEqualityExpression.class)) {
continue;
}
String val = getExpressionVarName(e);
if (val == null) {
continue;
}
ASTPrimarySuffix foo = se.getFirstDescendantOfType(ASTPrimarySuffix.class);
if (foo != null && foo.isArrayDereference()) {
continue;
}
if (val.equals(varName)) {
Node md = parameter.getFirstParentOfType(ASTMethodDeclaration.class);
if (md == null) {
md = pe.getFirstParentOfType(ASTConstructorDeclaration.class);
}
if (!isLocalVariable(varName, md)) {
addViolation(ctx, parameter, varName);
}
}
}
}
}
return false;
}
private ASTFormalParameter[] getArrays(ASTFormalParameters params) {
final List<ASTFormalParameter> l = params.findChildrenOfType(ASTFormalParameter.class);
if (l != null && !l.isEmpty()) {
List<ASTFormalParameter> l2 = new ArrayList<>();
for (ASTFormalParameter fp : l) {
if (fp.isArray()) {
l2.add(fp);
}
}
return l2.toArray(new ASTFormalParameter[l2.size()]);
}
return null;
}
}