Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"ruleKey": "S9149",
"hasTruePositives": true,
"falseNegatives": 0,
"falsePositives": 0
"falsePositives": 1
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,5 @@
432,
487,
512
],
"commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanUtils2TestCase.java": [
57
],
"commons-beanutils:src/test/java/org/apache/commons/beanutils2/WrapDynaBeanTestCase.java": [
75
]
}
51 changes: 0 additions & 51 deletions its/ruling/src/test/resources/expected/guava/java-S9149.json

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,18 @@ static void process(Unknown param) { // Compliant - parameter type is unknown
}
}

// --- Noncompliant: unknown return types are not considered covariant ---

static class ParentWithUnknownReturn {
static Unknown create() {
return null;
}
}

static class ChildWithUnknownReturn extends ParentWithUnknownReturn {
static Unknown create() { // Noncompliant {{Rename this method; it hides "create" in "ParentWithUnknownReturn".}}
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import com.google.errorprone.annotations.DoNotCall;
import com.google.errorprone.annotations.InlineMe;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import org.jetbrains.annotations.ApiStatus;

class StaticMethodHidingCheckSample {
Expand Down Expand Up @@ -331,4 +336,68 @@ static class PlainChild extends PlainParent {
static void compute() { // Noncompliant {{Rename this method; it hides "compute" in "PlainParent".}}
}
}

// --- Compliant: static factory returning a more specific type ---

static class ImmutableCollectionLike {
static ImmutableCollectionLike of() {
return new ImmutableCollectionLike();
}

static Collection<String> copyOf(Collection<String> elements) {
return elements;
}

static Object create() {
return new Object();
}
}

static class ImmutableSetLike extends ImmutableCollectionLike {
static ImmutableSetLike of() { // Compliant - covariant static factory
return new ImmutableSetLike();
}

static List<String> copyOf(Collection<String> elements) { // Compliant - covariant static factory
return new ArrayList<>(elements);
}

static Object create() { // Noncompliant {{Rename this method; it hides "create" in "ImmutableCollectionLike".}}
return new Object();
}
}

static class UnrelatedReturnParent {
static Integer value() {
return 1;
}
}

static class UnrelatedReturnChild extends UnrelatedReturnParent {
static Integer value() { // Noncompliant {{Rename this method; it hides "value" in "UnrelatedReturnParent".}}
return 2;
}
}
Comment on lines +370 to +380

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Quality: "Unrelated return type" test actually uses identical return types

UnrelatedReturnParent.value() and UnrelatedReturnChild.value() both return Integer, so this fixture repeats the same-return-type case that ImmutableSetLike.create() already covers (Object vs Object). The PR description says hiding with an "unrelated return type" is still reported, but no test checks that. Java (JLS 8.4.8.3) also rejects hiding with a return type that is not return-type-substitutable, so that case can only happen in non-compiling code. Either rename the fixture to say it covers the same return type, or move a real unrelated-return case (e.g. Integer vs String) into files/non-compiling/checks/StaticMethodHidingCheckSample.java so the stated behavior is actually tested.

Rename the fixture so it describes what it tests:

static class SameReturnParent {
  static Integer value() {
    return 1;
  }
}

static class SameReturnChild extends SameReturnParent {
  static Integer value() { // Noncompliant {{Rename this method; it hides "value" in "SameReturnParent".}}
    return 2;
  }
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎


// --- Compliant: JUnit 3 suite() methods have to be redeclared in each test class ---

public static class ParentTestCase extends TestCase {
public static Test suite() {
return new TestSuite(ParentTestCase.class);
}

public static Test other() {
return new TestSuite(ParentTestCase.class);
}
}

public static class ChildTestCase extends ParentTestCase {
public static Test suite() { // Compliant - JUnit 3 suite
return new TestSuite(ChildTestCase.class);
}

public static Test other() { // Noncompliant {{Rename this method; it hides "other" in "ParentTestCase".}}
return new TestSuite(ChildTestCase.class);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ private boolean checkHiding(MethodTree methodTree, Symbol.MethodSymbol methodSym
for (Symbol symbol : superClass.symbol().lookupSymbols(methodSymbol.name())) {
if (symbol.isMethodSymbol() && symbol.isStatic() && !symbol.isPrivate()
&& hasSameParameterTypes(methodSymbol, (Symbol.MethodSymbol) symbol)) {
reportHidingIssue(methodTree, methodSymbol, (Symbol.MethodSymbol) symbol);
if (!isRequiredByDesign(methodSymbol, (Symbol.MethodSymbol) symbol)) {
reportHidingIssue(methodTree, methodSymbol, (Symbol.MethodSymbol) symbol);
}
return true;
}
}
Expand Down Expand Up @@ -88,6 +90,26 @@ private static boolean isIntentionalHiding(Symbol.MethodSymbol methodSymbol) {
|| methodSymbol.metadata().isAnnotatedWith("org.jetbrains.annotations.ApiStatus$ScheduledForRemoval");
}

/**
* Some hierarchies are designed so that subclasses have to redeclare static methods of their parent:
* <ul>
* <li>static factories returning a more specific type than the hidden one (e.g. Guava's {@code ImmutableBiMap.of()} hiding {@code ImmutableMap.of()})</li>
* <li>JUnit 3 {@code public static Test suite()} methods, looked up by the test runner on each test class</li>
* </ul>
*/
private static boolean isRequiredByDesign(Symbol.MethodSymbol methodSymbol, Symbol.MethodSymbol hiddenMethod) {
Type returnType = methodSymbol.returnType().type().erasure();
Type hiddenReturnType = hiddenMethod.returnType().type().erasure();
boolean isCovariantFactory = !returnType.equals(hiddenReturnType) && returnType.isSubtypeOf(hiddenReturnType);
return isCovariantFactory || isJUnit3Suite(methodSymbol);
}

private static boolean isJUnit3Suite(Symbol.MethodSymbol methodSymbol) {
return "suite".equals(methodSymbol.name())
&& methodSymbol.parameterTypes().isEmpty()
&& methodSymbol.returnType().type().is("junit.framework.Test");
}

private static boolean hasSameParameterTypes(Symbol.MethodSymbol method, Symbol.MethodSymbol candidate) {
List<Type> methodParams = method.parameterTypes();
List<Type> candidateParams = candidate.parameterTypes();
Expand Down
Loading