All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sonar.java.checks.ThreadAsRunnableArgumentCheck Maven / Gradle / Ivy

/*
 * SonarQube Java
 * Copyright (C) 2012-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the Sonar Source-Available License for more details.
 *
 * You should have received a copy of the Sonar Source-Available License
 * along with this program; if not, see https://sonarsource.com/license/ssal/
 */
package org.sonar.java.checks;

import java.text.MessageFormat;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.Arguments;
import org.sonar.plugins.java.api.tree.AssignmentExpressionTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
import org.sonar.plugins.java.api.tree.MethodInvocationTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.NewArrayTree;
import org.sonar.plugins.java.api.tree.NewClassTree;
import org.sonar.plugins.java.api.tree.ReturnStatementTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.VariableTree;
import org.sonar.plugins.java.api.tree.YieldStatementTree;

@Rule(key = "S2438")
public class ThreadAsRunnableArgumentCheck extends IssuableSubscriptionVisitor {

  private static final String RUNNABLE_TYPE = "java.lang.Runnable";
  private static final String THREAD_TYPE = "java.lang.Thread";
  private static final String RUNNABLE_ARRAY_TYPE = RUNNABLE_TYPE + "[]";
  private static final String THREAD_ARRAY_TYPE = THREAD_TYPE + "[]";

  @Override
  public List nodesToVisit() {
    return List.of(Tree.Kind.VARIABLE, Tree.Kind.RETURN_STATEMENT, Tree.Kind.YIELD_STATEMENT, Tree.Kind.ASSIGNMENT,
      Tree.Kind.METHOD_INVOCATION, Tree.Kind.NEW_CLASS, Tree.Kind.NEW_ARRAY);
  }

  @Override
  public void visitNode(Tree tree) {
    switch(tree.kind()) {
      case VARIABLE -> visitVariable((VariableTree) tree);
      case RETURN_STATEMENT -> visitReturnStatement((ReturnStatementTree) tree);
      case YIELD_STATEMENT -> visitYieldStatement((YieldStatementTree) tree);
      case NEW_ARRAY -> visitNewArray((NewArrayTree) tree);
      case ASSIGNMENT -> {
        var assignment = (AssignmentExpressionTree) tree;
        checkTypeCoercion(assignment.variable().symbolType(), assignment.expression());
      }
      case METHOD_INVOCATION -> {
        var invocation = (MethodInvocationTree) tree;
        visitInvocation(invocation.methodSymbol(), invocation.arguments());
      }
      case NEW_CLASS -> {
        var invocation = (NewClassTree) tree;
        visitInvocation(invocation.methodSymbol(), invocation.arguments());
      }
    }
  }

  private void visitVariable(VariableTree tree) {
    var initializer = tree.initializer();
    if (initializer != null) {
      checkTypeCoercion(tree.symbol().type(), initializer);
    }
  }

  private void visitInvocation(Symbol.MethodSymbol methodSymbol, Arguments rhsValues) {
    List lhsTypes = methodSymbol.parameterTypes();

    var nonVarargCount = lhsTypes.size() - (methodSymbol.isVarArgsMethod() ? 1 : 0);
    for (int i = 0; i < nonVarargCount; i++) {
      checkTypeCoercion(lhsTypes.get(i), rhsValues.get(i));
    }
    var argumentCount = rhsValues.size();
    if (!methodSymbol.isVarArgsMethod() || argumentCount == nonVarargCount) {
      return;
    }

    var arrayType = (Type.ArrayType) lhsTypes.get(nonVarargCount);
    var elementType = arrayType.elementType();
    checkTypeCoercion(arrayType, rhsValues.get(nonVarargCount));
    for (int i = nonVarargCount; i < argumentCount; i++) {
      checkTypeCoercion(elementType, rhsValues.get(i));
    }
  }

  private void visitNewArray(NewArrayTree tree) {
    var lhsType = tree.type();
    if (lhsType != null && lhsType.symbolType().is(RUNNABLE_TYPE)) {
      tree.initializers().forEach(rhsValue -> checkTypeCoercion(lhsType.symbolType(), rhsValue));
    }
  }

  private void visitReturnStatement(ReturnStatementTree tree) {
    var expression = tree.expression();
    if (expression == null) {
      return;
    }

    Tree enclosing = ExpressionUtils.getEnclosingTree(tree, Tree.Kind.METHOD, Tree.Kind.LAMBDA_EXPRESSION);
    if (enclosing != null) {
      var lhsType = enclosing instanceof LambdaExpressionTree lambda ?
        lambda.symbol().returnType().type() : ((MethodTree) enclosing).returnType().symbolType();
      checkTypeCoercion(lhsType, expression);
    }
  }

  private void visitYieldStatement(YieldStatementTree tree) {
    Tree enclosing = ExpressionUtils.getEnclosingTree(tree, Tree.Kind.SWITCH_EXPRESSION, Tree.Kind.SWITCH_STATEMENT);
    if (enclosing == null || enclosing.is(Tree.Kind.SWITCH_STATEMENT)) {
      // Iside of a SwitchStatementTree, the `yield` is implicit and does not return a value.
      // Unlike SwitchExpressionTree, SwitchStatementTree is not an ExpressionTree.
      return;
    }
    var lhsType = ((ExpressionTree) enclosing).symbolType();
    checkTypeCoercion(lhsType, tree.expression());
  }

  private void checkTypeCoercion(Type lhsType, ExpressionTree rhsValue) {
    var rhsType = rhsValue.symbolType();
    if ((lhsType.is(RUNNABLE_TYPE) && isNonNullSubtypeOf(rhsType, THREAD_TYPE)) ||
      (lhsType.is(RUNNABLE_ARRAY_TYPE) && isNonNullSubtypeOf(rhsType, THREAD_ARRAY_TYPE))) {
      var message = MessageFormat.format("Replace this {0} instance with an instance of {1}.", rhsType.name(), lhsType.name());
      context.reportIssue(this, rhsValue, message);
    }
  }

  private static boolean isNonNullSubtypeOf(Type type, String superTypeName) {
    return !type.isNullType() && type.isSubtypeOf(superTypeName);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy