Hi,
I want to implement a rule to detect that the internal Field of a static final object have been set.
In fact, I have already implemented the detection of this rule in the same file. But in the vast majority of cases, it returns a static final object and sets it in other classes. It requires cross-file analysis.
I want to know if it can be achieved and how to do.
Java 17
SonarQube 10.2
@Rule(key = "StaticFinalObjectAttributeWriteCheck")
public class StaticFinalObjectAttributeWriteCheck extends IssuableSubscriptionVisitor {
private static final List<MethodMatcher> METHOD_MATCHERS = getMethodInvocationMatchers();
private static List<MethodMatcher> getMethodInvocationMatchers() {
return ImmutableList.of(
collectionMethodInvocation("add"),
collectionMethodInvocation("remove"),
collectionMethodInvocation("set"),
collectionMethodInvocation("retail"),
collectionMethodInvocation("replace"),
collectionMethodInvocation("put"),
collectionMethodInvocation("clear"),
collectionMethodInvocation("offer"),
collectionMethodInvocation("poll")
);
}
public StaticFinalObjectAttributeWriteCheck() {
}
private static MethodMatcher collectionMethodInvocation(String methodName) {
return MethodMatcher
.create()
.typeDefinition(TypeCriteria.subtypeOf("java.util.Collection"))
.name(NameCriteria.startsWith(methodName))
.withAnyParameters();
}
@Override
public List<Tree.Kind> nodesToVisit() {
ImmutableList.Builder<Tree.Kind> builder = ImmutableList.builder();
builder.add(Tree.Kind.METHOD_INVOCATION);
return builder.build();
}
@Override
public void visitNode(Tree tree) {
if (tree.is(Tree.Kind.METHOD_INVOCATION)) {
MethodInvocationTree mit = (MethodInvocationTree) tree;
if (! mit.methodSelect().is(Tree.Kind.MEMBER_SELECT)) {
return;
}
MemberSelectExpressionTree mset = (MemberSelectExpressionTree) mit.methodSelect();
if (!mset.expression().is(Tree.Kind.IDENTIFIER)) {
return;
}
IdentifierTree expression = (IdentifierTree) mset.expression();
IdentifierTree identifier = mset.identifier();
if (isStaticFinal(expression)) {
if (isSetterLike(identifier.symbol().name())) {
reportIssue(mset, "Set static final object's field could cause problems.");
} else if (methodMatchForCollection(mit)) {
reportIssue(mset, "Writing into static final collection could cause problems.");
}
}
}
}
private static boolean isStaticFinal(IdentifierTree identifierTree) {
Symbol symbol = identifierTree.symbol();
return symbol.isFinal() && symbol.isStatic();
}
private static boolean methodMatchForCollection(MethodInvocationTree methodInvocation) {
for (MethodMatcher methodMatcher : METHOD_MATCHERS) {
if (methodMatcher.matches(methodInvocation)) {
return true;
}
}
return false;
}
private static boolean isSetterLike(String methodName) {
return methodName.length() > 3 && methodName.startsWith("set");
}
}