/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.java.rule.sunsecure;
import java.util.List;
import org.jaxen.JaxenException;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.java.ast.ASTAllocationExpression;
import net.sourceforge.pmd.lang.java.ast.ASTArrayInitializer;
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
import net.sourceforge.pmd.lang.java.ast.ASTFieldDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTMethodDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTName;
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.ASTReturnStatement;
import net.sourceforge.pmd.lang.java.ast.ASTTypeDeclaration;
import net.sourceforge.pmd.lang.java.ast.ASTVariableDeclaratorId;
import net.sourceforge.pmd.lang.java.ast.ASTVariableInitializer;
/**
* Implementation note: this rule currently ignores return types of y.x.z,
* currently it handles only local type fields. Created on Jan 17, 2005
*
* @author mgriffa
*/
public class MethodReturnsInternalArrayRule extends AbstractSunSecureRule {
@Override
public Object visit(ASTClassOrInterfaceDeclaration node, Object data) {
if (node.isInterface()) {
return data;
}
return super.visit(node, data);
}
@Override
public Object visit(ASTMethodDeclaration method, Object data) {
if (!method.getResultType().returnsArray() || method.isPrivate()) {
return data;
}
List<ASTReturnStatement> returns = method.findDescendantsOfType(ASTReturnStatement.class);
ASTTypeDeclaration td = method.getFirstParentOfType(ASTTypeDeclaration.class);
for (ASTReturnStatement ret : returns) {
final String vn = getReturnedVariableName(ret);
if (!isField(vn, td)) {
continue;
}
if (ret.findDescendantsOfType(ASTPrimarySuffix.class).size() > 2) {
continue;
}
if (ret.hasDescendantOfType(ASTAllocationExpression.class)) {
continue;
}
if (hasArraysCopyOf(ret)) {
continue;
}
if (hasClone(ret, vn)) {
continue;
}
if (isEmptyArray(vn, td)) {
continue;
}
if (!isLocalVariable(vn, method)) {
addViolation(data, ret, vn);
} else {
// This is to handle field hiding
final ASTPrimaryPrefix pp = ret.getFirstDescendantOfType(ASTPrimaryPrefix.class);
if (pp != null && pp.usesThisModifier()) {
final ASTPrimarySuffix ps = ret.getFirstDescendantOfType(ASTPrimarySuffix.class);
if (ps.hasImageEqualTo(vn)) {
addViolation(data, ret, vn);
}
}
}
}
return data;
}
private boolean hasClone(ASTReturnStatement ret, String varName) {
List<ASTPrimaryExpression> expressions = ret.findDescendantsOfType(ASTPrimaryExpression.class);
for (ASTPrimaryExpression e : expressions) {
if (e.jjtGetChild(0) instanceof ASTPrimaryPrefix && e.jjtGetNumChildren() == 2
&& e.jjtGetChild(1) instanceof ASTPrimarySuffix
&& ((ASTPrimarySuffix) e.jjtGetChild(1)).isArguments()
&& ((ASTPrimarySuffix) e.jjtGetChild(1)).getArgumentCount() == 0) {
ASTName name = e.getFirstDescendantOfType(ASTName.class);
if (name != null && name.hasImageEqualTo(varName + ".clone")) {
return true;
}
}
}
return false;
}
private boolean hasArraysCopyOf(ASTReturnStatement ret) {
List<ASTPrimaryExpression> expressions = ret.findDescendantsOfType(ASTPrimaryExpression.class);
for (ASTPrimaryExpression e : expressions) {
if (e.jjtGetNumChildren() == 2 && e.jjtGetChild(0) instanceof ASTPrimaryPrefix
&& e.jjtGetChild(0).jjtGetNumChildren() == 1 && e.jjtGetChild(0).jjtGetChild(0) instanceof ASTName
&& e.jjtGetChild(0).jjtGetChild(0).getImage().endsWith("Arrays.copyOf")) {
return true;
}
}
return false;
}
private boolean isEmptyArray(String varName, ASTTypeDeclaration typeDeclaration) {
final List<ASTFieldDeclaration> fds = typeDeclaration.findDescendantsOfType(ASTFieldDeclaration.class);
if (fds != null) {
for (ASTFieldDeclaration fd : fds) {
final ASTVariableDeclaratorId vid = fd.getFirstDescendantOfType(ASTVariableDeclaratorId.class);
if (vid != null && vid.hasImageEqualTo(varName)) {
ASTVariableInitializer initializer = fd.getFirstDescendantOfType(ASTVariableInitializer.class);
if (initializer != null && initializer.jjtGetNumChildren() == 1) {
Node child = initializer.jjtGetChild(0);
if (child instanceof ASTArrayInitializer && child.jjtGetNumChildren() == 0) {
return true;
} else if (child instanceof ASTExpression) {
try {
List<? extends Node> arrayAllocation = child.findChildNodesWithXPath(
"./PrimaryExpression/PrimaryPrefix/AllocationExpression/ArrayDimsAndInits/Expression/PrimaryExpression/PrimaryPrefix/Literal[@IntLiteral=\"true\"][@Image=\"0\"]");
if (arrayAllocation != null && arrayAllocation.size() == 1) {
return true;
}
} catch (JaxenException e) {
return false;
}
}
}
}
}
}
return false;
}
}