From f8601c336862271277c3e83e9dfd8baa3f41c6ca Mon Sep 17 00:00:00 2001 From: leonardo-pilastri-sonarsource <115481625+leonardo-pilastri-sonarsource@users.noreply.github.com> Date: Wed, 22 Nov 2023 16:43:32 +0100 Subject: [PATCH] SONARJAVA-4700 Update API with methods from JUtils (#4559) --- .../AnonymousClassShouldBeLambdaCheck.java | 2 +- .../java/checks/ArrayForVarArgCheck.java | 3 +- .../checks/CallOuterPrivateMethodCheck.java | 5 +- .../CollectionInappropriateCallsCheck.java | 2 +- .../java/checks/ConfusingVarargCheck.java | 2 +- .../ConstructorCallingOverridableCheck.java | 2 +- .../java/checks/DiamondOperatorCheck.java | 3 +- .../checks/RedundantAbstractMethodCheck.java | 2 +- .../RedundantThrowsDeclarationCheck.java | 2 +- .../checks/SynchronizedOverrideCheck.java | 3 +- .../checks/ThreadAsRunnableArgumentCheck.java | 3 +- .../sonar/java/checks/VarCanBeUsedCheck.java | 2 +- .../checks/tests/NoTestInTestClassCheck.java | 3 +- .../unused/UnusedMethodParameterCheck.java | 3 +- .../java/model/JInitializerBlockSymbol.java | 25 +++++++ .../org/sonar/java/model/JMethodSymbol.java | 26 +++++++ .../java/org/sonar/java/model/JUtils.java | 24 ------ .../java/org/sonar/java/model/Symbols.java | 25 +++++++ .../plugins/java/api/semantic/Symbol.java | 25 +++++++ .../sonar/java/model/JMethodSymbolTest.java | 75 +++++++++++++++++++ .../org/sonar/java/model/JSymbolTest.java | 5 ++ .../java/org/sonar/java/model/JUtilsTest.java | 42 +++++------ .../model/declaration/MethodTreeImplTest.java | 5 +- .../org/sonar/java/se/FlowComputation.java | 3 +- .../se/checks/ParameterNullnessCheck.java | 3 +- .../sonar/java/se/xproc/BehaviorCache.java | 3 +- .../java/se/ExplodedGraphWalkerTest.java | 2 +- .../main/resources/static/documentation.md | 5 ++ 28 files changed, 228 insertions(+), 77 deletions(-) diff --git a/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassShouldBeLambdaCheck.java b/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassShouldBeLambdaCheck.java index 7f86f466daf..dd2a5d143e1 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassShouldBeLambdaCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AnonymousClassShouldBeLambdaCheck.java @@ -104,7 +104,7 @@ private static boolean hasSingleAbstractMethodInHierarchy(Set superTypes) // remove objects methods redefined in interfaces .filter(symbol -> !isObjectMethod(symbol)) // remove generic methods, which can not be written as lambda (JLS-11 ยง15.27) - .filter(symbol -> !JUtils.isParametrizedMethod(symbol)) + .filter(symbol -> !symbol.isParametrizedMethod()) // always take same symbol if method is redeclared over and over in hierarchy .map(AnonymousClassShouldBeLambdaCheck::overridenSymbolIfAny) .collect(Collectors.toSet()) diff --git a/java-checks/src/main/java/org/sonar/java/checks/ArrayForVarArgCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ArrayForVarArgCheck.java index a021cf33519..f2032312ed6 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ArrayForVarArgCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ArrayForVarArgCheck.java @@ -21,7 +21,6 @@ import org.sonar.check.Rule; import org.sonar.java.model.ExpressionUtils; -import org.sonar.java.model.JUtils; import org.sonar.java.model.LiteralUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Symbol; @@ -98,7 +97,7 @@ private void reportIssueForSameType(Symbol.MethodSymbol methodSymbol, NewArrayTr private static boolean isLastArgumentVarargs(Symbol.MethodSymbol methodSymbol, Arguments args) { // If we have less arguments than parameter types, it means that no arguments was pass to the varargs. // If we have more, the last argument can not be an array. - return !args.isEmpty() && JUtils.isVarArgsMethod(methodSymbol) && args.size() == methodSymbol.parameterTypes().size(); + return !args.isEmpty() && methodSymbol.isVarArgsMethod() && args.size() == methodSymbol.parameterTypes().size(); } private static Type getLastParameterType(List list) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/CallOuterPrivateMethodCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CallOuterPrivateMethodCheck.java index 380ce502438..8d963ed6d92 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CallOuterPrivateMethodCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CallOuterPrivateMethodCheck.java @@ -28,7 +28,6 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.java.model.ExpressionUtils; -import org.sonar.java.model.JUtils; import org.sonar.java.model.JavaTree; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; @@ -96,7 +95,7 @@ public void visitMethodInvocation(MethodInvocationTree tree) { String name = ExpressionUtils.methodName(tree).name(); unknownInvocations.computeIfAbsent(name, k -> new HashSet<>()).add(tree); } else if (isPrivateMethodOfOuterClass(symbol) && isInvocationOnCurrentInstance(tree)) { - if (JUtils.isParametrizedMethod(symbol) && symbol.declaration() != null) { + if (symbol.isParametrizedMethod() && symbol.declaration() != null) { // generic methods requires to use their declaration symbol rather than the parameterized one symbol = symbol.declaration().symbol(); } @@ -139,7 +138,7 @@ private boolean hasSameArity(Symbol.MethodSymbol methodUsed, MethodInvocationTre int formalArity = methodUsed.parameterTypes().size(); int invokedArity = mit.arguments().size(); return formalArity == invokedArity || - (JUtils.isVarArgsMethod(methodUsed) && invokedArity >= formalArity - 1); + (methodUsed.isVarArgsMethod() && invokedArity >= formalArity - 1); } private void reportIssueOnMethod(@Nullable MethodTree declaration, Symbol.TypeSymbol classSymbol) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/CollectionInappropriateCallsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/CollectionInappropriateCallsCheck.java index c0c8b5d7065..f86d19f21e9 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/CollectionInappropriateCallsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/CollectionInappropriateCallsCheck.java @@ -129,7 +129,7 @@ private static String typeNameWithParameters(Type type) { private static boolean isCallToParametrizedOrUnknownMethod(ExpressionTree expressionTree) { if (expressionTree.is(Tree.Kind.METHOD_INVOCATION)) { Symbol.MethodSymbol symbol = ((MethodInvocationTree) expressionTree).methodSymbol(); - return symbol.isUnknown() || JUtils.isParametrizedMethod(symbol); + return symbol.isUnknown() || symbol.isParametrizedMethod(); } return false; } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ConfusingVarargCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ConfusingVarargCheck.java index 66a8f710ab7..e321b978819 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ConfusingVarargCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ConfusingVarargCheck.java @@ -84,7 +84,7 @@ public void visitNode(Tree tree) { } private void checkConfusingVararg(Symbol.MethodSymbol method, Arguments arguments) { - if (!JUtils.isVarArgsMethod(method)) { + if (!method.isVarArgsMethod()) { return; } List parameterTypes = method.parameterTypes(); diff --git a/java-checks/src/main/java/org/sonar/java/checks/ConstructorCallingOverridableCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ConstructorCallingOverridableCheck.java index c6c5f7c9e28..5bc38f7623a 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ConstructorCallingOverridableCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ConstructorCallingOverridableCheck.java @@ -77,7 +77,7 @@ public void visitMethodInvocation(MethodInvocationTree tree) { } if (isInvocationOnSelf) { Symbol symbol = methodIdentifier.symbol(); - if (symbol.isMethodSymbol() && JUtils.isOverridable((Symbol.MethodSymbol) symbol) && isMethodDefinedOnConstructedType(symbol)) { + if (symbol.isMethodSymbol() && ((Symbol.MethodSymbol) symbol).isOverridable() && isMethodDefinedOnConstructedType(symbol)) { reportIssue(methodIdentifier, "Remove this call from a constructor to the overridable \"" + methodIdentifier.name() + "\" method."); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/DiamondOperatorCheck.java b/java-checks/src/main/java/org/sonar/java/checks/DiamondOperatorCheck.java index 5bd5408047e..e1982481108 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/DiamondOperatorCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/DiamondOperatorCheck.java @@ -28,7 +28,6 @@ import org.sonar.plugins.java.api.JavaVersionAwareVisitor; import org.sonar.java.ast.visitors.SubscriptionVisitor; import org.sonar.java.checks.helpers.QuickFixHelper; -import org.sonar.java.model.JUtils; import org.sonar.java.model.JavaTree; import org.sonar.java.reporting.JavaQuickFix; import org.sonar.java.reporting.JavaTextEdit; @@ -132,7 +131,7 @@ private static boolean usedAsArgumentWithoutDiamond(NewClassTree newClassTree) { return false; } - if (JUtils.isParametrizedMethod(methodSymbol)) { + if (methodSymbol.isParametrizedMethod()) { // killing the noise - might be required for inference on nested method calls return false; } diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantAbstractMethodCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantAbstractMethodCheck.java index 3254a44938a..08c7096c41e 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantAbstractMethodCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantAbstractMethodCheck.java @@ -68,7 +68,7 @@ private static boolean differentContract(Symbol.MethodSymbol method, Symbol.Meth } private static boolean removingParametrizedAspect(Symbol.MethodSymbol method, Symbol.MethodSymbol overridee) { - return !JUtils.isParametrizedMethod(method) && JUtils.isParametrizedMethod(overridee); + return !method.isParametrizedMethod() && overridee.isParametrizedMethod(); } private static boolean differentThrows(Symbol.MethodSymbol method, Symbol.MethodSymbol overridee) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/RedundantThrowsDeclarationCheck.java b/java-checks/src/main/java/org/sonar/java/checks/RedundantThrowsDeclarationCheck.java index 6811c29f065..1b879cb58ec 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/RedundantThrowsDeclarationCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/RedundantThrowsDeclarationCheck.java @@ -81,7 +81,7 @@ public void visitNode(Tree tree) { private void checkMethodThrownList(MethodTree methodTree, ListTree thrownList) { Set thrownExceptions = thrownExceptionsFromBody(methodTree); - boolean isOverridableMethod = JUtils.isOverridable(methodTree.symbol()); + boolean isOverridableMethod = methodTree.symbol().isOverridable(); Set undocumentedExceptionNames = new Javadoc(methodTree).undocumentedThrownExceptions(); Set reported = new HashSet<>(); diff --git a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedOverrideCheck.java b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedOverrideCheck.java index 26c9a552062..9e94978ca39 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/SynchronizedOverrideCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/SynchronizedOverrideCheck.java @@ -21,7 +21,6 @@ import java.util.Collections; import org.sonar.check.Rule; -import org.sonar.java.model.JUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; import org.sonar.plugins.java.api.semantic.Symbol; @@ -50,7 +49,7 @@ public void visitNode(Tree tree) { return; } Symbol.MethodSymbol overriddenSymbol = overriddenSymbols.get(0); - if (JUtils.isSynchronizedMethod(overriddenSymbol) && !JUtils.isSynchronizedMethod(methodSymbol)) { + if (overriddenSymbol.isSynchronizedMethod() && !methodSymbol.isSynchronizedMethod()) { List secondaries = Collections.emptyList(); MethodTree overridenMethodTree = overriddenSymbol.declaration(); if (overridenMethodTree != null) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/ThreadAsRunnableArgumentCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ThreadAsRunnableArgumentCheck.java index 31377b33f8a..3a07be9c591 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ThreadAsRunnableArgumentCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ThreadAsRunnableArgumentCheck.java @@ -20,7 +20,6 @@ package org.sonar.java.checks; import org.sonar.check.Rule; -import org.sonar.java.model.JUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; @@ -67,7 +66,7 @@ private void checkArgumentsTypes(List arguments, Symbol.MethodSy for (int index = 0; index < arguments.size(); index++) { ExpressionTree argument = arguments.get(index); Type providedType = argument.symbolType(); - if (!argument.is(Tree.Kind.NULL_LITERAL) && isThreadAsRunnable(providedType, parametersTypes, index, JUtils.isVarArgsMethod(methodSymbol))) { + if (!argument.is(Tree.Kind.NULL_LITERAL) && isThreadAsRunnable(providedType, parametersTypes, index, methodSymbol.isVarArgsMethod())) { reportIssue(argument, getMessage(argument, providedType, index)); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/VarCanBeUsedCheck.java b/java-checks/src/main/java/org/sonar/java/checks/VarCanBeUsedCheck.java index caf6e509b82..d7c5b536743 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/VarCanBeUsedCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/VarCanBeUsedCheck.java @@ -103,7 +103,7 @@ public void visitNode(Tree tree) { private static boolean isExcludedInitializer(ExpressionTree initializer) { if (initializer.is(Tree.Kind.METHOD_INVOCATION)) { Symbol.MethodSymbol symbol = ((MethodInvocationTree) initializer).methodSymbol(); - return !symbol.isUnknown() && JUtils.isParametrizedMethod(symbol); + return !symbol.isUnknown() && symbol.isParametrizedMethod(); } return initializer.is(Tree.Kind.CONDITIONAL_EXPRESSION, Tree.Kind.METHOD_REFERENCE, Tree.Kind.LAMBDA_EXPRESSION); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java b/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java index 3d8b1275416..ec147212c9d 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/tests/NoTestInTestClassCheck.java @@ -30,7 +30,6 @@ import org.apache.commons.lang3.StringUtils; import org.sonar.check.Rule; import org.sonar.check.RuleProperty; -import org.sonar.java.model.JUtils; import org.sonar.java.model.ModifiersUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.semantic.Symbol; @@ -226,7 +225,7 @@ private static Stream getAllMembers(Symbol.TypeSymbol symbol, boolean is } Stream defaultMethodsFromInterfaces = symbol.interfaces().stream() .flatMap(i -> getAllMembers(i.symbol(), false, visitedSymbols)) - .filter(m -> m.isMethodSymbol() && JUtils.isDefaultMethod((Symbol.MethodSymbol) m)); + .filter(m -> m.isMethodSymbol() && ((Symbol.MethodSymbol) m).isDefaultMethod()); members = Stream.concat(members, defaultMethodsFromInterfaces); for (Symbol s : symbol.memberSymbols()) { if (isNested(s) || isPublicStaticConcrete(s)) { diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java index 2b83501dc70..8a648bb1a96 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedMethodParameterCheck.java @@ -30,7 +30,6 @@ import org.sonar.java.checks.helpers.MethodTreeUtils; import org.sonar.java.checks.helpers.QuickFixHelper; import org.sonar.java.checks.helpers.UnresolvedIdentifiersVisitor; -import org.sonar.java.model.JUtils; import org.sonar.java.model.ModifiersUtils; import org.sonar.java.reporting.AnalyzerMessage; import org.sonar.java.reporting.JavaQuickFix; @@ -89,7 +88,7 @@ public void visitNode(Tree tree) { return; } Set undocumentedParameters = new HashSet<>(new Javadoc(methodTree).undocumentedParameters()); - boolean overridableMethod = JUtils.isOverridable(methodTree.symbol()); + boolean overridableMethod = methodTree.symbol().isOverridable(); List unused = new ArrayList<>(); for (VariableTree parameter : methodTree.parameters()) { Symbol symbol = parameter.symbol(); diff --git a/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java index 25061754805..161a71b42b8 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JInitializerBlockSymbol.java @@ -185,4 +185,29 @@ public String name() { public String signature() { return owner.name() + "." + name(); } + + @Override + public boolean isOverridable() { + return false; + } + + @Override + public boolean isParametrizedMethod() { + return false; + } + + @Override + public boolean isDefaultMethod() { + return false; + } + + @Override + public boolean isSynchronizedMethod() { + return false; + } + + @Override + public boolean isVarArgsMethod() { + return false; + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java index c66624d3ae4..d142fe8485c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java @@ -31,6 +31,7 @@ import org.eclipse.jdt.core.dom.ASTUtils; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.Modifier; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; @@ -176,6 +177,31 @@ public String signature() { return signature; } + @Override + public boolean isOverridable() { + return !isUnknown() && !(isPrivate() || isStatic() || isFinal() || owner().isFinal()); + } + + @Override + public boolean isParametrizedMethod() { + return !isUnknown() && (methodBinding().isParameterizedMethod() || methodBinding().isGenericMethod()); + } + + @Override + public boolean isDefaultMethod() { + return !isUnknown() && Modifier.isDefault(binding.getModifiers()); + } + + @Override + public boolean isSynchronizedMethod() { + return !isUnknown() && Modifier.isSynchronized(binding.getModifiers()); + } + + @Override + public boolean isVarArgsMethod() { + return !isUnknown() && methodBinding().isVarargs(); + } + @Nullable @Override public MethodTree declaration() { diff --git a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java index ba8f8e24248..34b14ea2c45 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java @@ -146,22 +146,10 @@ public static Symbol getPackage(Symbol symbol) { return symbol; } - public static boolean isVarArgsMethod(Symbol.MethodSymbol method) { - return !method.isUnknown() && ((JMethodSymbol) method).methodBinding().isVarargs(); - } - - public static boolean isSynchronizedMethod(Symbol.MethodSymbol method) { - return !method.isUnknown() && Modifier.isSynchronized(((JMethodSymbol) method).binding.getModifiers()); - } - public static boolean isNativeMethod(Symbol.MethodSymbol method) { return !method.isUnknown() && Modifier.isNative(((JMethodSymbol) method).binding.getModifiers()); } - public static boolean isDefaultMethod(Symbol.MethodSymbol method) { - return !method.isUnknown() && Modifier.isDefault(((JMethodSymbol) method).binding.getModifiers()); - } - @Nullable public static Object defaultValue(Symbol.MethodSymbol method) { if (method.isUnknown()) { @@ -170,18 +158,6 @@ public static Object defaultValue(Symbol.MethodSymbol method) { return ((JMethodSymbol) method).methodBinding().getDefaultValue(); } - public static boolean isOverridable(Symbol.MethodSymbol method) { - return !method.isUnknown() && !(method.isPrivate() || method.isStatic() || method.isFinal() || method.owner().isFinal()); - } - - public static boolean isParametrizedMethod(Symbol.MethodSymbol method) { - if (method.isUnknown()) { - return false; - } - return ((JMethodSymbol) method).methodBinding().isParameterizedMethod() - || ((JMethodSymbol) method).methodBinding().isGenericMethod(); - } - public static Set directSuperTypes(Type type) { if (type.isUnknown()) { return Collections.emptySet(); diff --git a/java-frontend/src/main/java/org/sonar/java/model/Symbols.java b/java-frontend/src/main/java/org/sonar/java/model/Symbols.java index 793212be0fd..2e998f81254 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/Symbols.java +++ b/java-frontend/src/main/java/org/sonar/java/model/Symbols.java @@ -294,6 +294,31 @@ public String name() { public String signature() { return "!unknownMethod!"; } + + @Override + public boolean isOverridable() { + return false; + } + + @Override + public boolean isParametrizedMethod() { + return false; + } + + @Override + public boolean isDefaultMethod() { + return false; + } + + @Override + public boolean isSynchronizedMethod() { + return false; + } + + @Override + public boolean isVarArgsMethod() { + return false; + } } private static final class UnknownType implements Type { diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java index 969dff46839..7744aa27b03 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/semantic/Symbol.java @@ -215,6 +215,31 @@ interface MethodSymbol extends Symbol { * @return the signature of the method, as String */ String signature(); + + /** + * @return true if the method symbol is overridable. + */ + boolean isOverridable(); + + /** + * @return true if the method has type parameters. + */ + boolean isParametrizedMethod(); + + /** + * @return true if the method has a default implementation. + */ + boolean isDefaultMethod(); + + /** + * @return true if the method is synchronized. + */ + boolean isSynchronizedMethod(); + + /** + * @return true if the method takes a vararg argument (e.g. `String... args`). + */ + boolean isVarArgsMethod(); } /** diff --git a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java index ceb199d3d44..62ee13bca0c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java @@ -353,6 +353,64 @@ void signature() { .isEqualTo("org.example.C#m(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"); } + @Test + void isOverridableTest(){ + JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isOverridable()).isTrue(); + jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }", true); + assertThat(jms.isOverridable()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { private C m() { return null; } }"); + assertThat(jms.isOverridable()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { static C m() { return null; } }"); + assertThat(jms.isOverridable()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { final C m() { return null; } }"); + assertThat(jms.isOverridable()).isFalse(); + jms = getJMethodSymbolFromClassText("final class C { C m() { return null; } }"); + assertThat(jms.isOverridable()).isFalse(); + } + + @Test + void isParametrizedMethodTest(){ + JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isParametrizedMethod()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isParametrizedMethod()).isTrue(); + jms = getJMethodSymbolFromClassText("class C { Collection m(T t) { return null; } }"); + assertThat(jms.isParametrizedMethod()).isTrue(); + jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }", true); + assertThat(jms.isParametrizedMethod()).isFalse(); + } + + @Test + void isDefaultMethodTest(){ + JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isDefaultMethod()).isFalse(); + jms = getJMethodSymbolFromClassText("interface I { default C m() { return null; } }"); + assertThat(jms.isDefaultMethod()).isTrue(); + jms = getJMethodSymbolFromClassText("interface I { default C m() { return null; } }", true); + assertThat(jms.isDefaultMethod()).isFalse(); + } + + @Test + void isSynchronizedMethodTest(){ + JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isSynchronizedMethod()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { synchronized C m() { return null; } }"); + assertThat(jms.isSynchronizedMethod()).isTrue(); + jms = getJMethodSymbolFromClassText("class C { synchronized C m() { return null; } }", true); + assertThat(jms.isSynchronizedMethod()).isFalse(); + } + + @Test + void isVarArgsTest(){ + JMethodSymbol jms = getJMethodSymbolFromClassText("class C { C m() { return null; } }"); + assertThat(jms.isVarArgsMethod()).isFalse(); + jms = getJMethodSymbolFromClassText("class C { C m(String... s) { return null; } }"); + assertThat(jms.isVarArgsMethod()).isTrue(); + jms = getJMethodSymbolFromClassText("class C { C m(String... s) { return null; } }", true); + assertThat(jms.isVarArgsMethod()).isFalse(); + } + @Test void support_unexpected_IMethodBinding_null_return_type() { JavaTree.CompilationUnitTreeImpl cu = test("class C { C m() { return null; } }"); @@ -385,6 +443,23 @@ void testParameterDeclarationsOfCompactConstructor() { assertThat(symbol.declarationParameters()).hasSize(6); } + + private static JMethodSymbol getJMethodSymbolFromClassText(String classText){ + return getJMethodSymbolFromClassText(classText, false); + } + private static JMethodSymbol getJMethodSymbolFromClassText(String classText, boolean brokenSemantic){ + JavaTree.CompilationUnitTreeImpl cu = test(classText); + ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); + MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); + if(brokenSemantic){ + IMethodBinding brokenMethodBinding = spy(m.methodBinding); + when(brokenMethodBinding.getReturnType()).thenReturn(null); + when(brokenMethodBinding.isRecovered()).thenReturn(true); + return new JMethodSymbol(cu.sema, brokenMethodBinding); + } + return new JMethodSymbol(cu.sema, m.methodBinding); + } + private static CompilationUnitTreeImpl test(String source) { return (CompilationUnitTreeImpl) JParserTestUtils.parse(source); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java index 7fb8fc4f3ae..545e43d0294 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java @@ -285,6 +285,11 @@ private void variable_in_class_initializer(boolean isStatic) { assertThat(initializerBlock.parameterTypes()).isEmpty(); assertThat(initializerBlock.declarationParameters()).isEmpty(); assertThat(initializerBlock.thrownTypes()).isEmpty(); + assertThat(initializerBlock.isOverridable()).isFalse(); + assertThat(initializerBlock.isParametrizedMethod()).isFalse(); + assertThat(initializerBlock.isDefaultMethod()).isFalse(); + assertThat(initializerBlock.isSynchronizedMethod()).isFalse(); + assertThat(initializerBlock.isVarArgsMethod()).isFalse(); } @Test diff --git a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java index 51767367b9b..d355a35cb6c 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java @@ -543,18 +543,18 @@ class IsVarArgsMethod { @Test void isVarArgsMethod() { MethodTreeImpl varArgsMethod = firstMethod(a); - assertThat(JUtils.isVarArgsMethod(varArgsMethod.symbol())).isTrue(); + assertThat(varArgsMethod.symbol().isVarArgsMethod()).isTrue(); } @Test void non_vararg_method_is_not_varargs() { MethodTreeImpl nonVarArgsMethod = nthMethod(a, 1); - assertThat(JUtils.isVarArgsMethod(nonVarArgsMethod.symbol())).isFalse(); + assertThat(nonVarArgsMethod.symbol().isVarArgsMethod()).isFalse(); } @Test void unknown_method_is_not_varargs() { - assertThat(JUtils.isVarArgsMethod(Symbols.unknownMethodSymbol)).isFalse(); + assertThat(Symbols.unknownMethodSymbol.isVarArgsMethod()).isFalse(); } } @@ -569,18 +569,18 @@ class IsSynchronizedMethod { @Test void isSynchronizedMethod() { MethodTreeImpl synchronizedMethod = firstMethod(a); - assertThat(JUtils.isSynchronizedMethod(synchronizedMethod.symbol())).isTrue(); + assertThat(synchronizedMethod.symbol().isSynchronizedMethod()).isTrue(); } @Test void not_synchronized() { MethodTreeImpl nonSynchronizedMethod = nthMethod(a, 1); - assertThat(JUtils.isSynchronizedMethod(nonSynchronizedMethod.symbol())).isFalse(); + assertThat(nonSynchronizedMethod.symbol().isSynchronizedMethod()).isFalse(); } @Test void unknown_method_is_not_synchronized() { - assertThat(JUtils.isSynchronizedMethod(Symbols.unknownMethodSymbol)).isFalse(); + assertThat(Symbols.unknownMethodSymbol.isSynchronizedMethod()).isFalse(); } } @@ -621,18 +621,18 @@ class IsDefaultMethod { @Test void isDefaultMethod() { MethodTreeImpl defaultMethod = firstMethod(a); - assertThat(JUtils.isDefaultMethod(defaultMethod.symbol())).isTrue(); + assertThat(defaultMethod.symbol().isDefaultMethod()).isTrue(); } @Test void not_default() { MethodTreeImpl nonDefaultMethod = nthMethod(a, 1); - assertThat(JUtils.isDefaultMethod(nonDefaultMethod.symbol())).isFalse(); + assertThat(nonDefaultMethod.symbol().isDefaultMethod()).isFalse(); } @Test void unknown_method_is_not_default() { - assertThat(JUtils.isDefaultMethod(Symbols.unknownMethodSymbol)).isFalse(); + assertThat(Symbols.unknownMethodSymbol.isDefaultMethod()).isFalse(); } } @@ -678,28 +678,28 @@ class IsOverridable { @Test void isOverridable() { MethodTreeImpl foo = firstMethod(a); - assertThat(JUtils.isOverridable(foo.symbol())).isTrue(); + assertThat(foo.symbol().isOverridable()).isTrue(); } @Test void private_method_is_not_overridable() { Symbol.MethodSymbol symbol = nthMethod(a, 1).symbol(); assertThat(symbol.isPrivate()).isTrue(); - assertThat(JUtils.isOverridable(symbol)).isFalse(); + assertThat(symbol.isOverridable()).isFalse(); } @Test void static_method_is_not_overridable() { Symbol.MethodSymbol symbol = nthMethod(a, 2).symbol(); assertThat(symbol.isStatic()).isTrue(); - assertThat(JUtils.isOverridable(symbol)).isFalse(); + assertThat(symbol.isOverridable()).isFalse(); } @Test void final_method_is_not_overridable() { Symbol.MethodSymbol symbol = nthMethod(a, 3).symbol(); assertThat(symbol.isFinal()).isTrue(); - assertThat(JUtils.isOverridable(symbol)).isFalse(); + assertThat(symbol.isOverridable()).isFalse(); } // final owner @@ -708,12 +708,12 @@ void method_from_record_is_not_overridable() { ClassTreeImpl b = nthClass(cu, 1); Symbol.MethodSymbol symbol = (firstMethod(b)).symbol(); assertThat(symbol.isFinal()).isFalse(); - assertThat(JUtils.isOverridable(symbol)).isFalse(); + assertThat(symbol.isOverridable()).isFalse(); } @Test void unknown_method_is_not_overridable() { - assertThat(JUtils.isOverridable(Symbols.unknownMethodSymbol)).isFalse(); + assertThat(Symbols.unknownMethodSymbol.isOverridable()).isFalse(); } } @@ -731,12 +731,12 @@ void isParametrizedMethod() { ExpressionStatementTreeImpl s = (ExpressionStatementTreeImpl) m.block().body().get(0); MethodInvocationTreeImpl methodInvocation = (MethodInvocationTreeImpl) s.expression(); - assertThat(JUtils.isParametrizedMethod(cu.sema.methodSymbol(m.methodBinding))) - .isEqualTo(JUtils.isParametrizedMethod(m.symbol())) + assertThat(cu.sema.methodSymbol(m.methodBinding).isParametrizedMethod()) + .isEqualTo(m.symbol().isParametrizedMethod()) .isEqualTo(m.methodBinding.isGenericMethod()) .isTrue(); - assertThat(JUtils.isParametrizedMethod(cu.sema.methodSymbol(methodInvocation.methodBinding))) - .isEqualTo(JUtils.isParametrizedMethod(methodInvocation.methodSymbol())) + assertThat(cu.sema.methodSymbol(methodInvocation.methodBinding).isParametrizedMethod()) + .isEqualTo(methodInvocation.methodSymbol().isParametrizedMethod()) .isEqualTo(methodInvocation.methodBinding.isParameterizedMethod()) .isTrue(); } @@ -744,12 +744,12 @@ void isParametrizedMethod() { @Test void non_parametrized_method_is_not_parametrized() { MethodTreeImpl n = nthMethod(c, 1); - assertThat(JUtils.isParametrizedMethod(n.symbol())).isFalse(); + assertThat(n.symbol().isParametrizedMethod()).isFalse(); } @Test void unknown_method_is_not_parametrized() { - assertThat(JUtils.isParametrizedMethod(Symbols.unknownMethodSymbol)).isFalse(); + assertThat(Symbols.unknownMethodSymbol.isParametrizedMethod()).isFalse(); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java b/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java index d4f628c4fcd..07076631e5d 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/declaration/MethodTreeImplTest.java @@ -22,7 +22,6 @@ import java.util.List; import org.junit.jupiter.api.Test; import org.sonar.java.model.JParserTestUtils; -import org.sonar.java.model.JUtils; import org.sonar.plugins.java.api.cfg.ControlFlowGraph; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ClassTree; @@ -244,9 +243,9 @@ void getLine_return_line_of_method_declaration() { @Test void varargs_flag() { Symbol.MethodSymbol methodSymbol = getUniqueMethod("class A { public static void main(String[] args){} }").symbol(); - assertThat(JUtils.isVarArgsMethod(methodSymbol)).isFalse(); + assertThat(methodSymbol.isVarArgsMethod()).isFalse(); methodSymbol = getUniqueMethod("class A { public static void main(String... args){} }").symbol(); - assertThat(JUtils.isVarArgsMethod(methodSymbol)).isTrue(); + assertThat(methodSymbol.isVarArgsMethod()).isTrue(); } private static MethodTreeImpl getUniqueMethod(String code) { diff --git a/java-symbolic-execution/src/main/java/org/sonar/java/se/FlowComputation.java b/java-symbolic-execution/src/main/java/org/sonar/java/se/FlowComputation.java index 6a88a24583a..8f5a5c2e8c2 100644 --- a/java-symbolic-execution/src/main/java/org/sonar/java/se/FlowComputation.java +++ b/java-symbolic-execution/src/main/java/org/sonar/java/se/FlowComputation.java @@ -42,7 +42,6 @@ import org.sonar.java.Preconditions; import org.sonar.java.cfg.CFG; import org.sonar.java.model.ExpressionUtils; -import org.sonar.java.model.JUtils; import org.sonar.java.se.ExplodedGraph.Node; import org.sonar.java.se.checks.SyntaxTreeNameFinder; import org.sonar.java.se.constraint.Constraint; @@ -813,7 +812,7 @@ public static Flow flowsForArgumentsChangingName(List argumentIndices, for (Integer argumentIndex : argumentIndices) { // do not consider varargs part - if (JUtils.isVarArgsMethod(methodSymbol) && argumentIndex >= methodParameters.size() - 1) { + if (methodSymbol.isVarArgsMethod() && argumentIndex >= methodParameters.size() - 1) { break; } IdentifierTree argumentName = getArgumentIdentifier(mit, argumentIndex); diff --git a/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/ParameterNullnessCheck.java b/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/ParameterNullnessCheck.java index 0e7fa7ba490..6abd3dabd68 100644 --- a/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/ParameterNullnessCheck.java +++ b/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/ParameterNullnessCheck.java @@ -24,7 +24,6 @@ import org.apache.commons.lang3.StringUtils; import org.sonar.check.Rule; import org.sonar.java.model.ExpressionUtils; -import org.sonar.java.model.JUtils; import org.sonar.java.se.CheckerContext; import org.sonar.java.se.Flow; import org.sonar.java.se.ProgramState; @@ -76,7 +75,7 @@ private void checkParameters(Tree syntaxNode, Symbol.MethodSymbol symbol, Argume } int nbArguments = arguments.size(); List argumentSVs = getArgumentSVs(state, syntaxNode, nbArguments); - int nbArgumentToCheck = Math.min(nbArguments, symbol.parameterTypes().size() - (JUtils.isVarArgsMethod(symbol) ? 1 : 0)); + int nbArgumentToCheck = Math.min(nbArguments, symbol.parameterTypes().size() - (symbol.isVarArgsMethod() ? 1 : 0)); List parameterSymbols = symbol.declarationParameters(); for (int i = 0; i < nbArgumentToCheck; i++) { ObjectConstraint constraint = state.getConstraint(argumentSVs.get(i), ObjectConstraint.class); diff --git a/java-symbolic-execution/src/main/java/org/sonar/java/se/xproc/BehaviorCache.java b/java-symbolic-execution/src/main/java/org/sonar/java/se/xproc/BehaviorCache.java index f687f918c60..0d0fab0e08e 100644 --- a/java-symbolic-execution/src/main/java/org/sonar/java/se/xproc/BehaviorCache.java +++ b/java-symbolic-execution/src/main/java/org/sonar/java/se/xproc/BehaviorCache.java @@ -38,7 +38,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.java.annotations.VisibleForTesting; -import org.sonar.java.model.JUtils; import org.sonar.java.se.SymbolicExecutionVisitor; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.MethodTree; @@ -72,7 +71,7 @@ Map hardcodedBehaviors() { public MethodBehavior methodBehaviorForSymbol(Symbol.MethodSymbol symbol) { String signature = symbol.signature(); - boolean varArgs = JUtils.isVarArgsMethod(symbol); + boolean varArgs = symbol.isVarArgsMethod(); return behaviors.computeIfAbsent(signature, k -> new MethodBehavior(signature, varArgs)); } diff --git a/java-symbolic-execution/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java b/java-symbolic-execution/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java index ac5a58fdbff..0939b8cd38e 100644 --- a/java-symbolic-execution/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java +++ b/java-symbolic-execution/src/test/java/org/sonar/java/se/ExplodedGraphWalkerTest.java @@ -779,7 +779,7 @@ private static JavaFileScanner[] seChecks() { } private static MethodBehavior methodBehaviorForSymbol(Symbol.MethodSymbol symbol) { - boolean varArgs = JUtils.isVarArgsMethod(symbol); + boolean varArgs = symbol.isVarArgsMethod(); return new MethodBehavior(symbol.signature(), varArgs); } } diff --git a/sonar-java-plugin/src/main/resources/static/documentation.md b/sonar-java-plugin/src/main/resources/static/documentation.md index 17ce5b77d3a..b97fe79f7a1 100644 --- a/sonar-java-plugin/src/main/resources/static/documentation.md +++ b/sonar-java-plugin/src/main/resources/static/documentation.md @@ -147,6 +147,11 @@ The tutorial [Writing Custom Java Rules 101](https://redirect.sonarsource.com/do All the API changes are related to ECJ utility methods that were commonly used in the analyzer and could benefit the implementation of custom rules. +* New Method `Symbol.MethodSymbol.isOverridable()`. Returns whether this method is overridable. +* New Method `Symbol.MethodSymbol.isVarArgsMethod()`. Returns whether this method has a vararg parameter. +* New Method `Symbol.MethodSymbol.isDefaultMethod()`. Returns whether this method has a default implementation. +* New Method `Symbol.MethodSymbol.isParametrizedMethod()`. Returns whether this method has type parameters. +* New Method `Symbol.MethodSymbol.isSynchronizedMethod()`. Returns whether this method is synchronized. * New method: `Type#isPrimitiveWrapper()`. Check if this type is a primitive wrapper. * New method: `Type#primitiveWrapperType()`. Returns the type of the primitive wrapper. * New method: `Type#primitiveType()`. Returns the type of the primitive.