diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java new file mode 100644 index 000000000000..abd323f67c33 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaReflectionInvocationInspection.java @@ -0,0 +1,228 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection.reflectiveAccess; + +import com.intellij.codeInspection.BaseJavaBatchLocalInspectionTool; +import com.intellij.codeInspection.InspectionsBundle; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import com.siyeh.ig.psiutils.MethodCallUtils; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Predicate; + +import static com.intellij.psi.CommonClassNames.JAVA_LANG_CLASS; +import static com.intellij.psi.CommonClassNames.JAVA_LANG_OBJECT; +import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*; + +/** + * @author Pavel.Dolgov + */ +public class JavaReflectionInvocationInspection extends BaseJavaBatchLocalInspectionTool { + + private static final String JAVA_LANG_REFLECT_METHOD = "java.lang.reflect.Method"; + private static final String JAVA_LANG_REFLECT_CONSTRUCTOR = "java.lang.reflect.Constructor"; + + private static final String INVOKE = "invoke"; + private static final String NEW_INSTANCE = "newInstance"; + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression methodCall) { + super.visitMethodCallExpression(methodCall); + + if (isCallToMethod(methodCall, JAVA_LANG_REFLECT_METHOD, INVOKE)) { + checkReflectionCall(methodCall, 1, holder, JavaReflectionInvocationInspection::isReflectionMethod); + } + else if (isCallToMethod(methodCall, JAVA_LANG_REFLECT_CONSTRUCTOR, NEW_INSTANCE)) { + checkReflectionCall(methodCall, 0, holder, JavaReflectionInvocationInspection::isReflectionConstructor); + } + } + }; + } + + private static boolean isReflectionMethod(PsiMethodCallExpression callExpression) { + return isCallToMethod(callExpression, JAVA_LANG_CLASS, GET_METHOD) || + isCallToMethod(callExpression, JAVA_LANG_CLASS, GET_DECLARED_METHOD); + } + + private static boolean isReflectionConstructor(PsiMethodCallExpression callExpression) { + return isCallToMethod(callExpression, JAVA_LANG_CLASS, GET_CONSTRUCTOR) || + isCallToMethod(callExpression, JAVA_LANG_CLASS, GET_DECLARED_CONSTRUCTOR); + } + + private static void checkReflectionCall(@NotNull PsiMethodCallExpression methodCall, + int argumentOffset, + @NotNull ProblemsHolder holder, + @NotNull Predicate methodPredicate) { + final List requiredTypes = + getRequiredMethodArguments(methodCall.getMethodExpression().getQualifierExpression(), argumentOffset, methodPredicate); + if (requiredTypes != null) { + final Arguments actualArguments = getActualMethodArguments(methodCall, argumentOffset); + if (actualArguments != null) { + + final PsiExpressionList argumentList = methodCall.getArgumentList(); + if (requiredTypes.size() != actualArguments.expressions.length) { + if (actualArguments.varargAsArray) { + final PsiExpression[] expressions = argumentList.getExpressions(); + final PsiElement element = expressions.length == argumentOffset + 1 ? expressions[argumentOffset] : argumentList; + holder.registerProblem(element, InspectionsBundle.message( + "inspection.reflection.invocation.item.count", requiredTypes.size())); + } + else { + holder.registerProblem(argumentList, InspectionsBundle.message( + "inspection.reflection.invocation.argument.count", requiredTypes.size() + argumentOffset)); + } + return; + } + + for (int i = 0; i < requiredTypes.size(); i++) { + final ReflectiveType requiredType = getReflectiveType(requiredTypes.get(i)); + if (requiredType != null) { + final PsiExpression argument = actualArguments.expressions[i]; + final PsiType actualType = argument.getType(); + if (actualType != null && !requiredType.isAssignableFrom(actualType)) { + if (PsiTreeUtil.isAncestor(argumentList, argument, false)) { + // either varargs or in-place arguments array + holder.registerProblem(argument, InspectionsBundle.message(actualArguments.varargAsArray + ? "inspection.reflection.invocation.item.not.assignable" + : "inspection.reflection.invocation.argument.not.assignable", + requiredType.getQualifiedName())); + } + else { + // arguments array in a variable + final PsiExpression[] expressions = argumentList.getExpressions(); + final PsiElement element = expressions.length == argumentOffset + 1 ? expressions[argumentOffset] : argumentList; + holder.registerProblem(element, InspectionsBundle.message( + "inspection.reflection.invocation.item.number.not.assignable", i, requiredType.getQualifiedName())); + } + } + } + } + } + } + } + + @Nullable + private static List getRequiredMethodArguments(@Nullable PsiExpression qualifier, + int argumentOffset, + @NotNull Predicate methodPredicate) { + final PsiExpression definition = findDefinition(ParenthesesUtils.stripParentheses(qualifier)); + if (definition instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression definitionCall = (PsiMethodCallExpression)definition; + if (methodPredicate.test(definitionCall)) { + final PsiExpression[] arguments = definitionCall.getArgumentList().getExpressions(); + + if (arguments.length == argumentOffset + 1) { + final PsiExpression[] arrayElements = getVarargAsArray(arguments[argumentOffset]); + if (arrayElements != null) { + return Arrays.asList(arrayElements); + } + } + if (arguments.length >= argumentOffset) { + return Arrays.asList(arguments).subList(argumentOffset, arguments.length); + } + } + } + return null; + } + + @Nullable + private static Arguments getActualMethodArguments(PsiMethodCallExpression methodCall, int argumentOffset) { + final PsiExpression[] arguments = methodCall.getArgumentList().getExpressions(); + if (arguments.length == argumentOffset + 1) { + final PsiExpression[] expressions = getVarargAsArray(arguments[argumentOffset]); + if (expressions != null) { + return new Arguments(expressions, true); + } + } + if (arguments.length >= argumentOffset) { + final PsiExpression[] expressions = argumentOffset != 0 ? Arrays.copyOfRange(arguments, argumentOffset, arguments.length) : arguments; + for (int i = 0; i < expressions.length; i++) { + final PsiExpression castOperand = unwrapDisambiguatingCastToObject(expressions[i]); + if (castOperand != null) { + expressions[i] = castOperand; + } + } + return new Arguments(expressions, false); + } + return null; + } + + @Nullable + private static PsiExpression[] getVarargAsArray(@Nullable PsiExpression maybeArray) { + if (maybeArray != null) { + final PsiType type = maybeArray.getType(); + if (type instanceof PsiArrayType && + type.getArrayDimensions() == 1 && + type.getDeepComponentType() instanceof PsiClassType) { + + final PsiExpression argumentsDefinition = findDefinition(maybeArray); + if (argumentsDefinition instanceof PsiArrayInitializerExpression) { + return ((PsiArrayInitializerExpression)argumentsDefinition).getInitializers(); + } + if (argumentsDefinition instanceof PsiNewExpression) { + final PsiArrayInitializerExpression arrayInitializer = ((PsiNewExpression)argumentsDefinition).getArrayInitializer(); + if (arrayInitializer != null) { + return arrayInitializer.getInitializers(); + } + final PsiExpression[] dimensions = ((PsiNewExpression)argumentsDefinition).getArrayDimensions(); + if (dimensions.length == 1) { // special case: new Object[0] + final Integer itemCount = computeConstantExpression(findDefinition(dimensions[0]), Integer.class); + if (itemCount != null && itemCount == 0) { + return PsiExpression.EMPTY_ARRAY; + } + } + } + } + } + return null; + } + + @Nullable + private static PsiExpression unwrapDisambiguatingCastToObject(@Nullable PsiExpression expression) { + if (expression instanceof PsiTypeCastExpression) { + final PsiTypeCastExpression typeCast = (PsiTypeCastExpression)expression; + final PsiTypeElement castElement = typeCast.getCastType(); + if (castElement != null && castElement.getType().equalsToText(JAVA_LANG_OBJECT)) { + return typeCast.getOperand(); + } + } + return null; + } + + private static boolean isCallToMethod(PsiMethodCallExpression methodCall, String className, String methodName) { + return MethodCallUtils.isCallToMethod(methodCall, className, null, methodName, (PsiType[])null); + } + + private static class Arguments { + final PsiExpression[] expressions; + final boolean varargAsArray; + + public Arguments(PsiExpression[] expressions, boolean varargAsArray) { + this.expressions = expressions; + this.varargAsArray = varargAsArray; + } + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java index 7718cee86a0e..1328cb519c9c 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceUtil.java @@ -27,10 +27,7 @@ import com.intellij.psi.*; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.JavaConstantExpressionEvaluator; import com.intellij.psi.search.GlobalSearchScope; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiTypesUtil; -import com.intellij.psi.util.PsiUtil; -import com.intellij.psi.util.PsiUtilCore; +import com.intellij.psi.util.*; import com.intellij.util.ArrayUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.PlatformIcons; @@ -80,6 +77,8 @@ public class JavaReflectionReferenceUtil { public static final String GET_DECLARED_FIELD = "getDeclaredField"; public static final String GET_METHOD = "getMethod"; public static final String GET_DECLARED_METHOD = "getDeclaredMethod"; + public static final String GET_CONSTRUCTOR = "getConstructor"; + public static final String GET_DECLARED_CONSTRUCTOR = "getDeclaredConstructor"; public static final String JAVA_LANG_CLASS_LOADER = "java.lang.ClassLoader"; public static final String FOR_NAME = "forName"; @@ -176,8 +175,10 @@ public class JavaReflectionReferenceUtil { @Nullable public static PsiExpression findDefinition(@Nullable PsiExpression expression) { - if (expression instanceof PsiReferenceExpression) { - return findVariableDefinition((PsiReferenceExpression)expression); + int preventEndlessLoop = 5; + while (expression instanceof PsiReferenceExpression) { + if (--preventEndlessLoop == 0) return null; + expression = findVariableDefinition((PsiReferenceExpression)expression); } return expression; } @@ -369,6 +370,21 @@ public class JavaReflectionReferenceUtil { return false; } + public boolean isAssignableFrom(@NotNull PsiType type) { + if (type.equals(PsiType.NULL)) { + return myPsiClass != null || myArrayDimensions != 0; + } + if (type.getArrayDimensions() != myArrayDimensions) { + return false; + } + final PsiType otherType = type.getDeepComponentType(); + if (myPrimitiveType != null) { + return myPrimitiveType.isAssignableFrom(otherType) || otherType.equalsToText(myPrimitiveType.getBoxedTypeName()); + } + final PsiElementFactory factory = JavaPsiFacade.getInstance(myPsiClass.getProject()).getElementFactory(); + return factory.createType(myPsiClass).isAssignableFrom(otherType); + } + @Nullable public static ReflectiveType create(@Nullable PsiType originalType, @NotNull PsiElement context) { if (originalType == null) { diff --git a/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamCount.java b/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamCount.java new file mode 100644 index 000000000000..1c1c76f41f9d --- /dev/null +++ b/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamCount.java @@ -0,0 +1,88 @@ +import java.lang.reflect.Method; + +import java.lang.reflect.Constructor; + +class ConstructorParamCount { + void varargSignature() throws Exception { + Class cls = Test.class; + + Constructor c1 = cls.getConstructor(int.class); + Constructor c2 = cls.getConstructor(int.class, String.class); + Constructor c3 = cls.getConstructor(int.class, String.class, String.class); + + c1.newInstance(42, "abc"); + c2.newInstance(42, "abc"); + c3.newInstance(42, "abc"); + + c1.newInstance(new Object[]{42, "abc"}); + c2.newInstance(new Object[]{42, "abc"}); + c3.newInstance(new Object[]{42, "abc"}); + + c1.newInstance(); + c2.newInstance(); + c3.newInstance(); + + cls.getConstructor(String.class).newInstance(new String[] {"abc"}); + } + + void arraySignatutre() throws Exception { + Class cls = Test.class; + + Constructor c1 = cls.getConstructor(new Class[]{int.class}); + Constructor c2 = cls.getConstructor(new Class[]{int.class, String.class}); + Constructor c3 = cls.getConstructor(new Class[]{int.class, String.class, String.class}); + + c1.newInstance(42, "abc"); + c2.newInstance(42, "abc"); + c3.newInstance(42, "abc"); + + c1.newInstance(new Object[]{42, "abc"}); + c2.newInstance(new Object[]{42, "abc"}); + c3.newInstance(new Object[]{42, "abc"}); + + c1.newInstance(); + c2.newInstance(); + c3.newInstance(); + + cls.getConstructor(new Class[]{String.class}).newInstance(new String[] {"abc"}); + } + + void manyArguments() throws Exception { + Class cls = M.class; + + Constructor m0 = cls.getConstructor(); + Constructor m1 = cls.getConstructor(int.class); + Constructor m2 = cls.getConstructor(int.class, short.class); + Constructor m3 = cls.getConstructor(int.class, short.class, long.class); + Constructor m4 = cls.getConstructor(int.class, short.class, long.class, float.class); + Constructor m5 = cls.getConstructor(int.class, short.class, long.class, float.class, double.class); + + m0.newInstance(0, 0); + m1.newInstance("abc"); + m2.newInstance(0, "abc"); + m3.newInstance(0, 0, "abc"); + m4.newInstance(0, 0, 0, "abc"); + m5.newInstance(0, 0, 0, 0, "abc"); + + Constructor m = cls.getConstructor(new Class[0]); + m.newInstance(new Object[0]); + m.newInstance(new Object[] {"abc"}); + } + + class Test { + public Test(int n) {} + public Test(int n, String s) {} + public Test(int n, String s, String t) {} + + public Test(String s) {} + } + + class M{ + public M() {} + public M(int a1) {} + public M(int a1, short a2) {} + public M(int a1, short a2, long a3) {} + public M(int a1, short a2, long a3, float a4) {} + public M(int a1, short a2, long a3, float a4, double a5) {} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamTypes.java b/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamTypes.java new file mode 100644 index 000000000000..57a33f282be6 --- /dev/null +++ b/java/java-tests/testData/inspection/javaReflectionInvocation/ConstructorParamTypes.java @@ -0,0 +1,131 @@ +import java.lang.reflect.Constructor; +import java.util.*; + +class ConstructorParamTypes { + void varargSignature() throws Exception { + Class cls = Test.class; + + Constructor c1 = cls.getConstructor(int.class, String.class); + Constructor c2 = cls.getConstructor(String.class, Integer.class); + Constructor c3 = cls.getConstructor(int[].class, List.class); + + c1.newInstance(42, "abc"); + c2.newInstance("abc", 42); + c3.newInstance(new int[]{42, 23}, Arrays.asList("x", "y")); + + c1.newInstance("42", "abc"); + c1.newInstance(42.5, "abc"); + c2.newInstance(42, 23); + c2.newInstance("abc", "def"); + c3.newInstance(Arrays.asList("x", "y"), new int[]{42, 23}); + c3.newInstance(42, "def"); + + + final Object[] a1 = {new Integer(42), "abc"}; + final Object[] a2 = {"abc", Integer.valueOf(42)}; + final Object[] a3 = {new int[]{42, 23}, Arrays.asList("x", "y")}; + c1.newInstance(a1); + c2.newInstance(a2); + c3.newInstance(a3); + + c1.newInstance(new Object[] {new Integer(42), "abc"}); + c2.newInstance(new Object[] {"abc", Integer.valueOf(42)}); + c3.newInstance(new Object[] {new int[]{42, 23}, Arrays.asList("x", "y")}); + + c1.newInstance(a2); + c2.newInstance(a3); + c3.newInstance(a1); + + c1.newInstance(new Object[]{"42", "abc"}); + c2.newInstance(new Object[]{42, 23}); + c3.newInstance(new Object[]{"x", "y"}); + + c1.newInstance(null, null); + c2.newInstance(null, null); + c3.newInstance(null, null); + + cls.getConstructor(String[].class).newInstance((Object)new String[] {"abc"}); + cls.getConstructor(String[].class).newInstance(new String[] {"abc"}); + } + + void arraySignatutre() throws Exception { + Class cls = Test.class; + + Constructor c1 = cls.getConstructor(new Class[]{int.class, String.class}); + Constructor c2 = cls.getConstructor(new Class[]{String.class, Integer.class}); + Constructor c3 = cls.getConstructor(new Class[]{int[].class, List.class}); + + c1.newInstance("42", "abc"); + c1.newInstance(42.5, "abc"); + c2.newInstance(42, 23); + c2.newInstance("abc", "def"); + c3.newInstance(Arrays.asList("x", "y"), new int[]{42, 23}); + c3.newInstance(42, "def"); + + final Object[] a1 = {new Integer(42), "abc"}; + final Object[] a2 = {"abc", Integer.valueOf(42)}; + final Object[] a3 = {new int[]{42, 23}, Arrays.asList("x", "y")}; + c1.newInstance(a1); + c2.newInstance(a2); + c3.newInstance(a3); + + c1.newInstance(new Object[] {new Integer(42), "abc"}); + c2.newInstance(new Object[] {"abc", Integer.valueOf(42)}); + c3.newInstance(new Object[] {new int[]{42, 23}, Arrays.asList("x", "y")}); + + c1.newInstance(a2); + c2.newInstance(a3); + c3.newInstance(a1); + + c1.newInstance(new Object[]{"42", "abc"}); + c2.newInstance(new Object[]{42, 23}); + c3.newInstance(new Object[]{"x", "y"}); + + c1.newInstance(null, null); + c2.newInstance(null, null); + c3.newInstance(null, null); + + cls.getConstructor(new Class[]{String[].class}).newInstance((Object)new String[] {"abc"}); + cls.getConstructor(new Class[]{String[].class}).newInstance(new String[] {"abc"}); + } + + void manyArguments() throws Exception { + Class cls = M.class; + + Constructor m0 = cls.getConstructor(); + Constructor m1 = cls.getConstructor(int.class); + Constructor m2 = cls.getConstructor(int.class, short.class); + Constructor m3 = cls.getConstructor(int.class, short.class, long.class); + Constructor m4 = cls.getConstructor(int.class, short.class, long.class, float.class); + Constructor m5 = cls.getConstructor(int.class, short.class, long.class, float.class, double.class); + + Object[] a0 = {}; + Object[] a1 = {"abc"}; + Object[] a2 = {0, "abc"}; + Object[] a3 = {0, (short)0, "abc"}; + Object[] a4 = {0, (short)0, 0, "abc"}; + Object[] a5 = {0, (short)0, 0, 0.0f, "abc"}; + + m0.newInstance(a0); + m1.newInstance(a1); + m2.newInstance(a2); + m3.newInstance(a3); + m4.newInstance(a4); + m5.newInstance(a5); + } + + class Test { + public Test(int n, String s) {} + public Test(String s, Integer n) {} + public Test(int[] n, List s) {} + public Test(String[] s) {} + } + + class M { + public String m1(int a1) {return "";} + public String m2(int a1, short a2) {return "";} + public String m3(int a1, short a2, long a3) {return "";} + public String m4(int a1, short a2, long a3, float a4) {return "";} + public String m5(int a1, short a2, long a3, float a4, double a5) {return "";} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamCount.java b/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamCount.java new file mode 100644 index 000000000000..38562b4a48a3 --- /dev/null +++ b/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamCount.java @@ -0,0 +1,89 @@ +import java.lang.reflect.Method; + +class MethodParamCount { + void varargSignature() throws Exception { + Class cls = Test.class; + Object obj = new Test(); + + Method m1 = cls.getMethod("bar", int.class); + Method m2 = cls.getMethod("bar", int.class, String.class); + Method m3 = cls.getMethod("bar", int.class, String.class, String.class); + + m1.invoke(obj, 42, "abc"); + m2.invoke(obj, 42, "abc"); + m3.invoke(obj, 42, "abc"); + + m1.invoke(obj, new Object[]{42, "abc"}); + m2.invoke(obj, new Object[]{42, "abc"}); + m3.invoke(obj, new Object[]{42, "abc"}); + + m1.invoke(obj); + m2.invoke(obj); + m3.invoke(obj); + + cls.getMethod("str", String.class).invoke(null, new String[] {"abc"}); + } + + void arraySignatutre() throws Exception { + Class cls = Test.class; + Object obj = new Test(); + + Method m1 = cls.getMethod("bar", new Class[]{int.class}); + Method m2 = cls.getMethod("bar", new Class[]{int.class, String.class}); + Method m3 = cls.getMethod("bar", new Class[]{int.class, String.class, String.class}); + + m1.invoke(obj, 42, "abc"); + m2.invoke(obj, 42, "abc"); + m3.invoke(obj, 42, "abc"); + + m1.invoke(obj, new Object[]{42, "abc"}); + m2.invoke(obj, new Object[]{42, "abc"}); + m3.invoke(obj, new Object[]{42, "abc"}); + + m1.invoke(obj); + m2.invoke(obj); + m3.invoke(obj); + + cls.getMethod("str", new Class[]{String.class}).invoke(null, new String[] {"abc"}); + } + + void manyArguments() throws Exception { + Class cls = M.class; + Object obj = new M(); + + Method m0 = cls.getMethod("m0"); + Method m1 = cls.getMethod("m1", int.class); + Method m2 = cls.getMethod("m2", int.class, short.class); + Method m3 = cls.getMethod("m3", int.class, short.class, long.class); + Method m4 = cls.getMethod("m4", int.class, short.class, long.class, float.class); + Method m5 = cls.getMethod("m5", int.class, short.class, long.class, float.class, double.class); + + m0.invoke(0, 0); + m1.invoke("abc"); + m2.invoke(0, "abc"); + m3.invoke(0, 0, "abc"); + m4.invoke(0, 0, 0, "abc"); + m5.invoke(0, 0, 0, 0, "abc"); + + Method m = cls.getMethod("m0", new Class[0]); + m.invoke(obj, new Object[0]); + m.invoke(obj, new Object[] {"abc"}); + } + + static class Test { + public void bar(int n) {} + public void bar(int n, String s) {} + public void bar(int n, String s, String t) {} + + public static void str(String s) {} + } + + class M { + public String m0() {return "";} + public String m1(int a1) {return "";} + public String m2(int a1, short a2) {return "";} + public String m3(int a1, short a2, long a3) {return "";} + public String m4(int a1, short a2, long a3, float a4) {return "";} + public String m5(int a1, short a2, long a3, float a4, double a5) {return "";} + } +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamTypes.java b/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamTypes.java new file mode 100644 index 000000000000..261998ff30f4 --- /dev/null +++ b/java/java-tests/testData/inspection/javaReflectionInvocation/MethodParamTypes.java @@ -0,0 +1,135 @@ +import java.lang.reflect.Method; +import java.util.*; + +class MethodParamTypes { + void varargSignature() throws Exception { + Class cls = Test.class; + Object obj = new Test(); + + Method m1 = cls.getMethod("bar", int.class, String.class); + Method m2 = cls.getMethod("bar", String.class, Integer.class); + Method m3 = cls.getMethod("bar", int[].class, List.class); + + m1.invoke(obj, 42, "abc"); + m2.invoke(obj, "abc", 42); + m3.invoke(obj, new int[]{42, 23}, Arrays.asList("x", "y")); + + m1.invoke(obj, "42", "abc"); + m1.invoke(obj, 42.5, "abc"); + m2.invoke(obj, 42, 23); + m2.invoke(obj, "abc", "def"); + m3.invoke(obj, Arrays.asList("x", "y"), new int[]{42, 23}); + m3.invoke(obj, 42, "def"); + + + final Object[] a1 = {new Integer(42), "abc"}; + final Object[] a2 = {"abc", Integer.valueOf(42)}; + final Object[] a3 = {new int[]{42, 23}, Arrays.asList("x", "y")}; + m1.invoke(obj, a1); + m2.invoke(obj, a2); + m3.invoke(obj, a3); + + m1.invoke(obj, new Object[] {new Integer(42), "abc"}); + m2.invoke(obj, new Object[] {"abc", Integer.valueOf(42)}); + m3.invoke(obj, new Object[] {new int[]{42, 23}, Arrays.asList("x", "y")}); + + m1.invoke(obj, a2); + m2.invoke(obj, a3); + m3.invoke(obj, a1); + + m1.invoke(obj, new Object[]{"42", "abc"}); + m2.invoke(obj, new Object[]{42, 23}); + m3.invoke(obj, new Object[]{"x", "y"}); + + m1.invoke(obj, null, null); + m2.invoke(obj, null, null); + m3.invoke(obj, null, null); + + cls.getMethod("str", String[].class).invoke(null, (Object)new String[] {"abc"}); + cls.getMethod("str", String[].class).invoke(null, new String[] {"abc"}); + } + + void arraySignatutre() throws Exception { + Class cls = Test.class; + Object obj = new Test(); + + Method m1 = cls.getMethod("bar", new Class[]{int.class, String.class}); + Method m2 = cls.getMethod("bar", new Class[]{String.class, Integer.class}); + Method m3 = cls.getMethod("bar", new Class[]{int[].class, List.class}); + + m1.invoke(obj, "42", "abc"); + m1.invoke(obj, 42.5, "abc"); + m2.invoke(obj, 42, 23); + m2.invoke(obj, "abc", "def"); + m3.invoke(obj, Arrays.asList("x", "y"), new int[]{42, 23}); + m3.invoke(obj, 42, "def"); + + final Object[] a1 = {new Integer(42), "abc"}; + final Object[] a2 = {"abc", Integer.valueOf(42)}; + final Object[] a3 = {new int[]{42, 23}, Arrays.asList("x", "y")}; + m1.invoke(obj, a1); + m2.invoke(obj, a2); + m3.invoke(obj, a3); + + m1.invoke(obj, new Object[] {new Integer(42), "abc"}); + m2.invoke(obj, new Object[] {"abc", Integer.valueOf(42)}); + m3.invoke(obj, new Object[] {new int[]{42, 23}, Arrays.asList("x", "y")}); + + m1.invoke(obj, a2); + m2.invoke(obj, a3); + m3.invoke(obj, a1); + + m1.invoke(obj, new Object[]{"42", "abc"}); + m2.invoke(obj, new Object[]{42, 23}); + m3.invoke(obj, new Object[]{"x", "y"}); + + m1.invoke(obj, null, null); + m2.invoke(obj, null, null); + m3.invoke(obj, null, null); + + cls.getMethod("str", new Class[]{String[].class}).invoke(null, (Object)new String[] {"abc"}); + cls.getMethod("str", new Class[]{String[].class}).invoke(null, new String[] {"abc"}); + } + + void manyArguments() throws Exception { + Class cls = M.class; + Object obj = new M(); + + Method m0 = cls.getMethod("m0"); + Method m1 = cls.getMethod("m1", int.class); + Method m2 = cls.getMethod("m2", int.class, short.class); + Method m3 = cls.getMethod("m3", int.class, short.class, long.class); + Method m4 = cls.getMethod("m4", int.class, short.class, long.class, float.class); + Method m5 = cls.getMethod("m5", int.class, short.class, long.class, float.class, double.class); + + Object[] a0 = {}; + Object[] a1 = {"abc"}; + Object[] a2 = {0, "abc"}; + Object[] a3 = {0, (short)0, "abc"}; + Object[] a4 = {0, (short)0, 0, "abc"}; + Object[] a5 = {0, (short)0, 0, 0.0f, "abc"}; + + m0.invoke(obj, a0); + m1.invoke(obj, a1); + m2.invoke(obj, a2); + m3.invoke(obj, a3); + m4.invoke(obj, a4); + m5.invoke(obj, a5); + } + + static class Test { + public void bar(int n, String s) {} + public void bar(String s, Integer n) {} + public void bar(int[] n, List s) {} + + public static void str(String[] s) {} + } + + class M { + public String m1(int a1) {return "";} + public String m2(int a1, short a2) {return "";} + public String m3(int a1, short a2, long a3) {return "";} + public String m4(int a1, short a2, long a3, float a4) {return "";} + public String m5(int a1, short a2, long a3, float a4, double a5) {return "";} + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/JavaReflectionInvocationTest.kt b/java/java-tests/testSrc/com/intellij/codeInspection/JavaReflectionInvocationTest.kt new file mode 100644 index 000000000000..4f0c396777db --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/JavaReflectionInvocationTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.codeInspection + +import com.intellij.JavaTestUtil +import com.intellij.codeInspection.reflectiveAccess.JavaReflectionInvocationInspection +import com.intellij.openapi.roots.LanguageLevelProjectExtension +import com.intellij.pom.java.LanguageLevel +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase + +/** + * @author Pavel.Dolgov + */ +class JavaReflectionInvocationTest : LightCodeInsightFixtureTestCase() { + override fun setUp() { + super.setUp() + LanguageLevelProjectExtension.getInstance(project).languageLevel = LanguageLevel.JDK_1_5 + myFixture.enableInspections(JavaReflectionInvocationInspection()) + } + + override fun getProjectDescriptor(): LightProjectDescriptor = LightCodeInsightFixtureTestCase.JAVA_8 + + override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/javaReflectionInvocation" + + fun testMethodParamCount() = doTest() + fun testMethodParamTypes() = doTest() + + fun testConstructorParamCount() = doTest() + fun testConstructorParamTypes() = doTest() + + private fun doTest() { + myFixture.testHighlighting("${getTestName(false)}.java") + } +} \ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index 87c4652d9208..0105b1969d71 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -812,4 +812,11 @@ inspection.handle.signature.replace.with.fix.name=Replace with ''{0}'' inspection.handle.signature.use.method.fix.family.name=Use one of method overloads inspection.handle.signature.use.method.fix.name=Use method ''{0}'' inspection.handle.signature.use.constructor.fix.family.name=Use one of constructor overloads -inspection.handle.signature.use.constructor.fix.name=Use constructor ''{0}'' \ No newline at end of file +inspection.handle.signature.use.constructor.fix.name=Use constructor ''{0}'' + +inspection.reflection.invocation.name=Reflective invocation arguments mismatch +inspection.reflection.invocation.argument.count={0,choice,0#No arguments are|1#One argument is|1<{0} arguments are} expected +inspection.reflection.invocation.item.count={0,choice,0#Empty array is|1#Single-item array is|1<{0} array items are} expected +inspection.reflection.invocation.argument.not.assignable=Argument is not assignable to ''{0}'' +inspection.reflection.invocation.item.not.assignable=Array item is not assignable to ''{0}'' +inspection.reflection.invocation.item.number.not.assignable=Array item {0} is not assignable to ''{1}'' \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/JavaReflectionInvocation.html b/resources-en/src/inspectionDescriptions/JavaReflectionInvocation.html new file mode 100644 index 000000000000..1a720645362e --- /dev/null +++ b/resources-en/src/inspectionDescriptions/JavaReflectionInvocation.html @@ -0,0 +1,15 @@ + + +The inspection checks that the arguments provided to Method.invoke() and Constructor.newInstance() +match the signature specified in Class.getMethod() and Class.getConstructor() + +

Example:

+

+  Method m = myObj.getClass().getMethod("myMethod", int.class);
+  // the argument should be an int value
+  m.invoke(myObj, "abc");
+
+ +

New in 2017.2

+ + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index dc2c2daa5187..03246d2622f3 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -922,6 +922,10 @@ groupPath="Java" groupBundle="messages.InspectionsBundle" groupKey="group.names.reflective.access.issues" bundle="messages.InspectionsBundle" key="inspection.handle.signature.name" implementationClass="com.intellij.codeInspection.reflectiveAccess.JavaLangInvokeHandleSignatureInspection"/> +