Detect static final object's set method

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");
    }
}

Hello there, currently we do not support proper cross-file analysis in our APIs. Nonetheless, you can probably achieve what you are trying to do with the EndOfAnalysis interface.
All checks implementing this interface’s only method endOfAnalysis will be called when the analysis of a module is completed (be mindful of this, analyzing multi-module projects means that each check will have its endOfAnalysis method invoked once per module).
This means that you can have the logic of your check store some information/state about the trees that you are visiting, through different files, and at the end of the analysis of the module, perform more logic to decide which issues you want to raise.

Sadly, the public API does not support yet issue reporting at the file level when in the end-of-analysis context. You can only report the issue at the module level, so what you can do is compute a nice and explanatory issue message, maybe reporting the issue coordinates.

This is an example of a very simple check that reports an issue on all methods that have the same name as another method in another file (basically only the first occurrence of a method name is allowed)

@Rule(key = "MethodNameOnlyOnce")
public class MethodNameOnlyOnce extends IssuableSubscriptionVisitor implements EndOfAnalysis {

  private final Map<String, List<String>> methodNamesToIssues = new HashMap<>();

  @Override
  public List<Tree.Kind> nodesToVisit() {
    return List.of(Tree.Kind.METHOD);
  }

  @Override
  public void visitNode(Tree tree) {
    MethodTree methodTree = (MethodTree) tree;
    String methodName = methodTree.simpleName().name();
    if (methodNamesToIssues.containsKey(methodName)) {
      String filename = "<<Some logic here to get the compilation unit containing the method>>";
      String line = "<<Some logic here to get the line number of the method>>";
      methodNamesToIssues.get(methodName).add(String.format("The method name '%s' in %s at %s  was already used in the project.", methodName, filename, line));
    } else {
      methodNamesToIssues.put(methodName, new ArrayList<>());
    }
  }

  @Override
  public void endOfAnalysis(ModuleScannerContext context) {
    methodNamesToIssues.values().forEach(issues -> issues.forEach(message -> context.addIssueOnProject(this, message)));
  }

}

Here the check stores every method name it encounters once, but if a method name was already found, it stores an issue message with the file and line coordinates of the issue, which will be reported at the end of the analysis.

Avoid storing in the state of your check any object that has a reference to a Tree, as that would effectively mean storing all of the AST, semantic model, and CFG linked to it, and could take up a lot of memory

I know this is not ideal, but let me know if it helps and if you manage to adapt it to your case!