From 2d7375a5262bf8ee399a49ec619a7d106741fbd5 Mon Sep 17 00:00:00 2001 From: Pavel Dolgov Date: Fri, 10 Mar 2017 20:58:18 +0300 Subject: [PATCH] Java: Implemented "MethodHandle/VarHandle type mismatch" inspection (IDEA-167318) --- ...vaLangInvokeHandleSignatureInspection.java | 494 ++++++++++++++++++ .../impl/JavaLangInvokeHandleReference.java | 28 +- .../JavaReflectionReferenceContributor.java | 5 +- .../impl/JavaReflectionReferenceUtil.java | 138 ++++- .../invokeHandleSignature/Constructor.java | 43 ++ .../invokeHandleSignature/GenericMethod.java | 25 + .../invokeHandleSignature/Getter.java | 47 ++ .../OverloadedMethod.java | 41 ++ .../invokeHandleSignature/Setter.java | 47 ++ .../invokeHandleSignature/StaticMethod.java | 24 + .../invokeHandleSignature/VarHandle.java | 47 ++ .../JavaLangInvokeHandleSignatureTest.kt | 56 ++ .../src/messages/InspectionsBundle.properties | 15 + .../JavaLangInvokeHandleSignature.html | 6 + resources/src/META-INF/IdeaPlugin.xml | 4 + 15 files changed, 983 insertions(+), 37 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/Constructor.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/GenericMethod.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/Getter.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/OverloadedMethod.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/Setter.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/StaticMethod.java create mode 100644 java/java-tests/testData/inspection/invokeHandleSignature/VarHandle.java create mode 100644 java/java-tests/testSrc/com/intellij/codeInspection/JavaLangInvokeHandleSignatureTest.kt create mode 100644 resources-en/src/inspectionDescriptions/JavaLangInvokeHandleSignature.html diff --git a/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java new file mode 100644 index 000000000000..15006b1216b0 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/reflectiveAccess/JavaLangInvokeHandleSignatureInspection.java @@ -0,0 +1,494 @@ +/* + * 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.codeInsight.daemon.JavaErrorMessages; +import com.intellij.codeInspection.*; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.JavaCodeStyleManager; +import com.intellij.psi.impl.JavaConstantExpressionEvaluator; +import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.containers.ContainerUtil; +import com.siyeh.ig.psiutils.ParenthesesUtils; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.stream.Collectors; + +import static com.intellij.psi.impl.source.resolve.reference.impl.JavaLangInvokeHandleReference.*; +import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*; + +/** + * @author Pavel.Dolgov + */ +public class JavaLangInvokeHandleSignatureInspection extends BaseJavaBatchLocalInspectionTool { + private static final String METHOD_TYPE = "methodType"; + private static final String GENERIC_METHOD_TYPE = "genericMethodType"; + + private static final String FIND_CONSTRUCTOR = "findConstructor"; + private static final Set KNOWN_METHOD_NAMES = Collections.unmodifiableSet( + ContainerUtil.union(Arrays.asList(HANDLE_FACTORY_METHOD_NAMES), Collections.singletonList(FIND_CONSTRUCTOR))); + + private static final List NO_ARGUMENT_CONSTRUCTOR_SIGNATURE = Collections.singletonList(PsiKeyword.VOID); + + @NotNull + @Override + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + @Override + public void visitMethodCallExpression(PsiMethodCallExpression callExpression) { + super.visitMethodCallExpression(callExpression); + + final PsiReferenceExpression methodExpression = callExpression.getMethodExpression(); + final String methodName = methodExpression.getReferenceName(); + if (methodName != null && KNOWN_METHOD_NAMES.contains(methodName)) { + final PsiMethod method = callExpression.resolveMethod(); + final PsiClass psiClass = method != null ? method.getContainingClass() : null; + if (psiClass != null && JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP.equals(psiClass.getQualifiedName())) { + final PsiExpression[] arguments = callExpression.getArgumentList().getExpressions(); + checkHandlerFactory(methodName, methodExpression, arguments, holder); + } + } + } + }; + } + + private static void checkHandlerFactory(@NotNull String factoryMethodName, + @NotNull PsiReferenceExpression factoryMethodExpression, + @NotNull PsiExpression[] arguments, + @NotNull ProblemsHolder holder) { + if (arguments.length == 2) { + if (FIND_CONSTRUCTOR.equals(factoryMethodName)) { + final PsiClass ownerClass = getReflectiveClass(arguments[0]); + if (ownerClass != null) { + final PsiExpression typeExpression = ParenthesesUtils.stripParentheses(arguments[1]); + checkConstructor(ownerClass, typeExpression, holder); + } + } + } + else if (arguments.length >= 3) { + final PsiClass ownerClass = getReflectiveClass(arguments[0]); + if (ownerClass != null) { + final PsiExpression nameExpression = ParenthesesUtils.stripParentheses(arguments[1]); + final PsiExpression nameDefinition = findDefinition(nameExpression); + final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(nameDefinition, false); + final String name = ObjectUtils.tryCast(value, String.class); + if (!StringUtil.isEmpty(name)) { + final PsiExpression typeExpression = ParenthesesUtils.stripParentheses(arguments[2]); + + switch (factoryMethodName) { + case FIND_GETTER: + case FIND_SETTER: + case FIND_VAR_HANDLE: + checkField(ownerClass, name, nameExpression, typeExpression, false, factoryMethodExpression, holder); + break; + case FIND_STATIC_GETTER: + case FIND_STATIC_SETTER: + case FIND_STATIC_VAR_HANDLE: + checkField(ownerClass, name, nameExpression, typeExpression, true, factoryMethodExpression, holder); + break; + + case FIND_VIRTUAL: + checkMethod(ownerClass, name, nameExpression, typeExpression, false, factoryMethodExpression, holder); + break; + + case FIND_STATIC: + checkMethod(ownerClass, name, nameExpression, typeExpression, true, factoryMethodExpression, holder); + break; + + case FIND_SPECIAL: + checkMethod(ownerClass, name, nameExpression, typeExpression, false, factoryMethodExpression, holder); + break; + } + } + } + } + } + + private static void checkConstructor(@NotNull PsiClass ownerClass, + @NotNull PsiExpression typeExpression, + @NotNull ProblemsHolder holder) { + final List methodSignature = extractMethodSignature(typeExpression); + if (methodSignature != null) { + final List constructors = ContainerUtil.filter(ownerClass.getMethods(), PsiMethod::isConstructor); + LocalQuickFix[] fixes = null; + if (constructors.isEmpty()) { + if (!methodSignature.equals(NO_ARGUMENT_CONSTRUCTOR_SIGNATURE)) { + final LocalQuickFix fix = ReplaceSignatureQuickFix.createConstructorSignatureFix(ownerClass, NO_ARGUMENT_CONSTRUCTOR_SIGNATURE); + fixes = fix != null ? new LocalQuickFix[]{fix} : LocalQuickFix.EMPTY_ARRAY; + } + } + else if (!matchMethodSignature(constructors, methodSignature)) { + fixes = constructors.stream() + .map(constructor -> ReplaceSignatureQuickFix.createConstructorSignatureFix(ownerClass, constructor)) + .filter(Objects::nonNull) + .toArray(LocalQuickFix[]::new); + } + if (fixes != null) { + final String declarationText = getConstructorDeclarationText(ownerClass, methodSignature); + if (declarationText != null) { + holder.registerProblem(typeExpression, JavaErrorMessages.message("cannot.resolve.constructor", declarationText), fixes); + } + } + } + } + + private static void checkField(@NotNull PsiClass ownerClass, + @NotNull String name, + @NotNull PsiExpression nameExpression, + @NotNull PsiExpression typeExpression, + boolean isStatic, + @NotNull PsiReferenceExpression factoryMethodExpression, + @NotNull ProblemsHolder holder) { + final PsiField field = ownerClass.findFieldByName(name, true); + if (field == null) { + holder.registerProblem(nameExpression, InspectionsBundle.message("inspection.handle.signature.field.cannot.resolve", name)); + return; + } + + if (field.hasModifierProperty(PsiModifier.STATIC) != isStatic) { + final String factoryMethodName = factoryMethodExpression.getReferenceName(); + final PsiElement factoryMethodNameElement = factoryMethodExpression.getReferenceNameElement(); + if (factoryMethodName != null && factoryMethodNameElement != null) { + final LocalQuickFix fix = SwitchStaticnessQuickFix.createFix(factoryMethodName, isStatic); + final String message = InspectionsBundle.message( + isStatic ? "inspection.handle.signature.field.static" : "inspection.handle.signature.field.not.static", name); + holder.registerProblem(factoryMethodNameElement, message, fix != null ? new LocalQuickFix[]{fix} : LocalQuickFix.EMPTY_ARRAY); + return; + } + } + + final ReflectiveType reflectiveType = getReflectiveType(typeExpression); + if (reflectiveType != null && !reflectiveType.isEqualTo(field.getType())) { + final String expectedTypeText = getTypeText(field.getType()); + if (expectedTypeText != null) { + final String message = InspectionsBundle.message("inspection.handle.signature.field.type", name, expectedTypeText); + holder.registerProblem(typeExpression, message, new FieldTypeQuickFix(expectedTypeText)); + } + } + } + + private static void checkMethod(@NotNull PsiClass ownerClass, + @NotNull String name, + @NotNull PsiExpression nameExpression, + @NotNull PsiExpression typeExpression, + boolean isStatic, + @NotNull PsiReferenceExpression factoryMethodExpression, + @NotNull ProblemsHolder holder) { + + final PsiMethod[] methods = ownerClass.findMethodsByName(name, true); + if (methods.length == 0) { + holder.registerProblem(nameExpression, JavaErrorMessages.message("cannot.resolve.method", name)); + return; + } + + final List filteredMethods = + ContainerUtil.filter(methods, method -> method.hasModifierProperty(PsiModifier.STATIC) == isStatic); + if (filteredMethods.isEmpty()) { + final String factoryMethodName = factoryMethodExpression.getReferenceName(); + final PsiElement factoryMethodNameElement = factoryMethodExpression.getReferenceNameElement(); + if (factoryMethodName != null && factoryMethodNameElement != null) { + final LocalQuickFix fix = SwitchStaticnessQuickFix.createFix(factoryMethodName, isStatic); + final String message = InspectionsBundle.message( + isStatic ? "inspection.handle.signature.method.static" : "inspection.handle.signature.method.not.static", name); + holder.registerProblem(factoryMethodNameElement, message, fix != null ? new LocalQuickFix[]{fix} : LocalQuickFix.EMPTY_ARRAY); + return; + } + } + + final List methodSignature = extractMethodSignature(typeExpression); + if (methodSignature != null && !matchMethodSignature(filteredMethods, methodSignature)) { + final String declarationText = getMethodDeclarationText(name, methodSignature); + if (declarationText != null) { + final LocalQuickFix[] fixes = filteredMethods.stream() + .map(ReplaceSignatureQuickFix::createMethodSignatureFix) + .filter(Objects::nonNull) + .toArray(LocalQuickFix[]::new); + + holder.registerProblem(typeExpression, JavaErrorMessages.message("cannot.resolve.method", declarationText), fixes); + } + } + } + + @Nullable + private static String getMethodDeclarationText(@NotNull String name, @NotNull List methodSignature) { + if (methodSignature.isEmpty()) { + return null; + } + final String argumentTypes = methodSignature.stream().skip(1).collect(Collectors.joining(", ")); + return methodSignature.get(0) + " " + name + "(" + argumentTypes + ")"; + } + + @Nullable + private static String getConstructorDeclarationText(@NotNull PsiClass ownerClass, List methodSignature) { + final String name = ownerClass.getName(); + if (name == null || methodSignature.isEmpty()) { + return null; + } + // Return type of the constructor should be 'void'. If it isn't so let's make that mistake more noticeable. + final String returnType = methodSignature.get(0); + final String fakeReturnType = !PsiKeyword.VOID.equals(returnType) ? returnType + " " : ""; + final String argumentTypes = methodSignature.stream().skip(1).collect(Collectors.joining(", ")); + return fakeReturnType + name + "(" + argumentTypes + ")"; + } + + private static boolean matchMethodSignature(@NotNull List methods, @NotNull List expectedMethodSignature) { + return methods.stream() + .map(JavaLangInvokeHandleSignatureInspection::extractMethodSignature) + .anyMatch(expectedMethodSignature::equals); + } + + /** + * Extract the types from arguments of MethodType.methodType(Class...) and MethodType.genericMethodType(int, boolean?) + */ + private static List extractMethodSignature(@Nullable PsiExpression typeExpression) { + final PsiExpression typeDefinition = findDefinition(typeExpression); + if (typeDefinition instanceof PsiMethodCallExpression) { + final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)typeDefinition; + final String referenceName = methodCallExpression.getMethodExpression().getReferenceName(); + final boolean isGeneric; + if (METHOD_TYPE.equals(referenceName)) { + isGeneric = false; + } + else if (GENERIC_METHOD_TYPE.equals(referenceName)) { + isGeneric = true; + } + else { + return null; + } + final PsiMethod method = methodCallExpression.resolveMethod(); + if (method != null) { + final PsiClass psiClass = method.getContainingClass(); + if (psiClass != null && JAVA_LANG_INVOKE_METHOD_TYPE.equals(psiClass.getQualifiedName())) { + final PsiExpression[] arguments = methodCallExpression.getArgumentList().getExpressions(); + return isGeneric ? extractGenericMethodSignature(arguments) : extractMethodSignature(arguments); + } + } + } + return null; + } + + @Nullable + private static List extractMethodSignature(PsiExpression[] arguments) { + final List typeNames = Arrays.stream(arguments) + .map(JavaLangInvokeHandleSignatureInspection::getTypeText) + .collect(Collectors.toList()); + return !typeNames.isEmpty() && !typeNames.contains(null) ? typeNames : null; + } + + private static List extractGenericMethodSignature(PsiExpression[] arguments) { + if (arguments.length == 0 || arguments.length > 2) { + return null; + } + + final PsiExpression countArgument = ParenthesesUtils.stripParentheses(arguments[0]); + final Object countArgumentValue = JavaConstantExpressionEvaluator.computeConstantExpression(countArgument, false); + if (!(countArgumentValue instanceof Integer)) { + return null; + } + final int objectArgCount = (int)countArgumentValue; + if (objectArgCount < 0 || objectArgCount > 255) { + return null; + } + + boolean finalArray = false; + if (arguments.length == 2) { + final PsiExpression hasArrayArgument = ParenthesesUtils.stripParentheses(arguments[1]); + final Object hasArrayArgumentValue = JavaConstantExpressionEvaluator.computeConstantExpression(hasArrayArgument, false); + if (!(hasArrayArgumentValue instanceof Boolean)) { + return null; + } + finalArray = (boolean)hasArrayArgumentValue; + if (finalArray && objectArgCount > 254) { + return null; + } + } + + final List typeNames = new ArrayList<>(); + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); // return type + for (int i = 0; i < objectArgCount; i++) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT); + } + if (finalArray) { + typeNames.add(CommonClassNames.JAVA_LANG_OBJECT + "[]"); + } + return typeNames; + } + + @Contract("null -> null") + @Nullable + private static List extractMethodSignature(@Nullable PsiMethod method) { + if (method != null) { + final List types = new ArrayList<>(); + final PsiType returnType = !method.isConstructor() ? method.getReturnType() : PsiType.VOID; + types.add(getTypeText(returnType)); + for (PsiParameter parameter : method.getParameterList().getParameters()) { + types.add(getTypeText(parameter.getType())); + } + if (!types.contains(null)) { + return types; + } + } + return null; + } + + @Nullable + private static String getTypeText(@Nullable PsiExpression argument) { + final ReflectiveType reflectiveType = getReflectiveType(argument); + return reflectiveType != null ? reflectiveType.getQualifiedName() : null; + } + + @Nullable + private static String getTypeText(@Nullable PsiType type) { + PsiType erased = TypeConversionUtil.erasure(type); + if (erased instanceof PsiEllipsisType) { + erased = ((PsiEllipsisType)erased).toArrayType(); + } + return erased != null ? erased.getCanonicalText() : null; + } + + private static class FieldTypeQuickFix implements LocalQuickFix { + private final String myFieldTypeText; + + public FieldTypeQuickFix(String fieldTypeText) {myFieldTypeText = fieldTypeText;} + + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionsBundle.message("inspection.handle.signature.change.type.fix.name", myFieldTypeText); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiExpression typeExpression = factory.createExpressionFromText(myFieldTypeText + ".class", element); + final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project); + styleManager.shortenClassReferences(element.replace(typeExpression)); + } + } + + private static class SwitchStaticnessQuickFix implements LocalQuickFix { + private static final Map STATIC_TO_NON_STATIC = ContainerUtil.immutableMapBuilder() + .put(FIND_STATIC_GETTER, FIND_GETTER) + .put(FIND_STATIC_SETTER, FIND_SETTER) + .put(FIND_STATIC_VAR_HANDLE, FIND_VAR_HANDLE) + .put(FIND_STATIC, FIND_VIRTUAL) + .build(); + private static final Map NON_STATIC_TO_STATIC = ContainerUtil.immutableMapBuilder() + .put(FIND_GETTER, FIND_STATIC_GETTER) + .put(FIND_SETTER, FIND_STATIC_SETTER) + .put(FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE) + .put(FIND_VIRTUAL, FIND_STATIC) + .build(); + + private final String myReplacementName; + + public SwitchStaticnessQuickFix(@NotNull String replacementName) { + myReplacementName = replacementName; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return InspectionsBundle.message("inspection.handle.signature.replace.with.fix.name", myReplacementName); + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiIdentifier identifier = factory.createIdentifier(myReplacementName); + final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project); + styleManager.shortenClassReferences(element.replace(identifier)); + } + + @Nullable + public static LocalQuickFix createFix(@NotNull String methodName, boolean isStatic) { + final String replacementName = isStatic ? STATIC_TO_NON_STATIC.get(methodName) : NON_STATIC_TO_STATIC.get(methodName); + return replacementName != null ? new SwitchStaticnessQuickFix(replacementName) : null; + } + } + + private static class ReplaceSignatureQuickFix implements LocalQuickFix { + private final String myFixName; + private final List myMethodSignature; + + public ReplaceSignatureQuickFix(@NotNull String fixName, @NotNull List methodSignature) { + myFixName = fixName; + myMethodSignature = methodSignature; + } + + @Nls + @NotNull + @Override + public String getFamilyName() { + return myFixName; + } + + @Override + public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { + final PsiElement element = descriptor.getPsiElement(); + + final String types = myMethodSignature.stream() + .map(text -> text + ".class") + .collect(Collectors.joining(", ")); + final String text = JAVA_LANG_INVOKE_METHOD_TYPE + "." + METHOD_TYPE + "(" + types + ")"; + + final PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); + final PsiExpression replacement = factory.createExpressionFromText(text, element); + final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project); + styleManager.shortenClassReferences(element.replace(replacement)); + } + + @Nullable + public static LocalQuickFix createMethodSignatureFix(@Nullable PsiMethod method) { + final List methodSignature = extractMethodSignature(method); + if (methodSignature != null) { + final String declarationText = getMethodDeclarationText(method.getName(), methodSignature); + if (declarationText != null) { + final String message = InspectionsBundle.message("inspection.handle.signature.use.method.fix.name", declarationText); + return new ReplaceSignatureQuickFix(message, methodSignature); + } + } + return null; + } + + @Nullable + public static LocalQuickFix createConstructorSignatureFix(@NotNull PsiClass ownerClass, @Nullable PsiMethod constructor) { + final List methodSignature = extractMethodSignature(constructor); + return methodSignature != null ? createConstructorSignatureFix(ownerClass, methodSignature) : null; + } + + @Nullable + public static LocalQuickFix createConstructorSignatureFix(@NotNull PsiClass ownerClass, @NotNull List methodSignature) { + final String declarationText = getConstructorDeclarationText(ownerClass, methodSignature); + if (declarationText != null) { + final String message = InspectionsBundle.message("inspection.handle.signature.use.constructor.fix.name", declarationText); + return new ReplaceSignatureQuickFix(message, methodSignature); + } + return null; + } + } +} diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java index 35796b1a04e5..be8bc11b98c5 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaLangInvokeHandleReference.java @@ -44,20 +44,26 @@ import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflection * @author Pavel.Dolgov */ public class JavaLangInvokeHandleReference extends PsiReferenceBase implements InsertHandler { - static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; - static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; + public static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java.lang.invoke.MethodHandles.Lookup"; + public static final String JAVA_LANG_INVOKE_METHOD_TYPE = "java.lang.invoke.MethodType"; - static final String FIND_VIRTUAL = "findVirtual"; - static final String FIND_STATIC = "findStatic"; - static final String FIND_SPECIAL = "findSpecial"; + public static final String FIND_VIRTUAL = "findVirtual"; + public static final String FIND_STATIC = "findStatic"; + public static final String FIND_SPECIAL = "findSpecial"; - static final String FIND_GETTER = "findGetter"; - static final String FIND_SETTER = "findSetter"; - static final String FIND_STATIC_GETTER = "findStaticGetter"; - static final String FIND_STATIC_SETTER = "findStaticSetter"; + public static final String FIND_GETTER = "findGetter"; + public static final String FIND_SETTER = "findSetter"; + public static final String FIND_STATIC_GETTER = "findStaticGetter"; + public static final String FIND_STATIC_SETTER = "findStaticSetter"; - static final String FIND_VAR_HANDLE = "findVarHandle"; - static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; + public static final String FIND_VAR_HANDLE = "findVarHandle"; + public static final String FIND_STATIC_VAR_HANDLE = "findStaticVarHandle"; + + public static final String[] HANDLE_FACTORY_METHOD_NAMES = { + FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL, + FIND_GETTER, FIND_SETTER, + FIND_STATIC_GETTER, FIND_STATIC_SETTER, + FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE}; private final PsiExpression myContext; diff --git a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java index 803a950e8f67..ab4da5d778ee 100644 --- a/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java +++ b/java/java-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/JavaReflectionReferenceContributor.java @@ -48,10 +48,7 @@ public class JavaReflectionReferenceContributor extends PsiReferenceContributor private static final ElementPattern METHOD_HANDLE_PATTERN = psiLiteral() .methodCallParameter(1, psiMethod() - .withName(FIND_VIRTUAL, FIND_STATIC, FIND_SPECIAL, - FIND_GETTER, FIND_SETTER, - FIND_STATIC_GETTER, FIND_STATIC_SETTER, - FIND_VAR_HANDLE, FIND_STATIC_VAR_HANDLE) + .withName(HANDLE_FACTORY_METHOD_NAMES) .definedInClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP)); @Override 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 37e1a57f912e..c4526cfcfa3d 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 @@ -26,10 +26,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.PsiUtilCore; -import com.intellij.psi.util.TypeConversionUtil; +import com.intellij.psi.util.*; import com.siyeh.ig.psiutils.DeclarationSearchUtils; import com.siyeh.ig.psiutils.ParenthesesUtils; import org.jetbrains.annotations.Contract; @@ -42,18 +39,18 @@ import java.util.stream.Collectors; /** * @author Pavel.Dolgov */ -class JavaReflectionReferenceUtil { +public class JavaReflectionReferenceUtil { private static final RecursionGuard ourGuard = RecursionManager.createGuard("JavaLangClassMemberReference"); @Nullable - static PsiClass getReflectiveClass(@Nullable PsiExpression context) { + public static ReflectiveType getReflectiveType(@Nullable PsiExpression context) { context = ParenthesesUtils.stripParentheses(context); if (context == null) { return null; } if (context instanceof PsiClassObjectAccessExpression) { // special case for JDK 1.4 - PsiTypeElement operand = ((PsiClassObjectAccessExpression)context).getOperand(); - return PsiTypesUtil.getPsiClass(operand.getType()); + final PsiTypeElement operand = ((PsiClassObjectAccessExpression)context).getOperand(); + return ReflectiveType.create(operand.getType(), context); } if (context instanceof PsiMethodCallExpression) { @@ -64,14 +61,10 @@ class JavaReflectionReferenceUtil { if (method != null && isJavaLangClass(method.getContainingClass())) { final PsiExpression[] expressions = methodCall.getArgumentList().getExpressions(); if (expressions.length == 1) { - PsiExpression argument = ParenthesesUtils.stripParentheses(expressions[0]); - if (argument instanceof PsiReferenceExpression) { - argument = findVariableDefinition(((PsiReferenceExpression)argument)); - } + final PsiExpression argument = findDefinition(ParenthesesUtils.stripParentheses(expressions[0])); final Object value = JavaConstantExpressionEvaluator.computeConstantExpression(argument, false); if (value instanceof String) { - final Project project = context.getProject(); - return JavaPsiFacade.getInstance(project).findClass((String)value, GlobalSearchScope.allScope(project)); + return ReflectiveType.create(findClass((String)value, context)); } } } @@ -83,21 +76,18 @@ class JavaReflectionReferenceUtil { if (qualifier instanceof PsiReferenceExpression) { final PsiExpression definition = findVariableDefinition((PsiReferenceExpression)qualifier); if (definition != null) { - final PsiClass actualClass = PsiTypesUtil.getPsiClass(definition.getType()); - if (actualClass != null) { - return actualClass; - } + return ReflectiveType.create(definition.getType(), context); } } //TODO type of the qualifier may be a supertype of the actual value - need to compute the type of the actual value // otherwise getDeclaredField and getDeclaredMethod may work not reliably if (qualifier != null) { - return PsiTypesUtil.getPsiClass(qualifier.getType()); + return ReflectiveType.create(qualifier.getType(), context); } } } } - PsiType type = context.getType(); + final PsiType type = context.getType(); if (type instanceof PsiClassType) { PsiClassType.ClassResolveResult resolveResult = ((PsiClassType)type).resolveGenerics(); if (!isJavaLangClass(resolveResult.getElement())) return null; @@ -109,7 +99,7 @@ class JavaReflectionReferenceUtil { } final PsiClass argumentClass = PsiTypesUtil.getPsiClass(typeArgument); if (argumentClass != null && !isJavaLangObject(argumentClass)) { - return argumentClass; + return ReflectiveType.create(argumentClass); } } } @@ -118,18 +108,34 @@ class JavaReflectionReferenceUtil { if (resolved instanceof PsiVariable) { final PsiExpression definition = findVariableDefinition((PsiReferenceExpression)context, (PsiVariable)resolved); if (definition != null) { - return ourGuard.doPreventingRecursion(resolved, false, () -> getReflectiveClass(definition)); + return ourGuard.doPreventingRecursion(resolved, false, () -> getReflectiveType(definition)); } } } return null; } + @Nullable + public static PsiClass getReflectiveClass(PsiExpression context) { + final ReflectiveType reflectiveType = getReflectiveType(context); + return reflectiveType != null ? reflectiveType.myPsiClass : null; + } + + @Nullable + public static PsiExpression findDefinition(@Nullable PsiExpression expression) { + if (expression instanceof PsiReferenceExpression) { + return findVariableDefinition((PsiReferenceExpression)expression); + } + return expression; + } + + @Nullable private static PsiExpression findVariableDefinition(@NotNull PsiReferenceExpression referenceExpression) { final PsiElement resolved = referenceExpression.resolve(); return resolved instanceof PsiVariable ? findVariableDefinition(referenceExpression, (PsiVariable)resolved) : null; } + @Nullable private static PsiExpression findVariableDefinition(@NotNull PsiReferenceExpression referenceExpression, @NotNull PsiVariable variable) { if (variable.hasModifierProperty(PsiModifier.FINAL)) { final PsiExpression initializer = variable.getInitializer(); @@ -140,6 +146,11 @@ class JavaReflectionReferenceUtil { return DeclarationSearchUtils.findDefinition(referenceExpression, variable); } + private static PsiClass findClass(@NotNull String qualifiedName, @NotNull PsiElement context) { + final Project project = context.getProject(); + return JavaPsiFacade.getInstance(project).findClass(qualifiedName, GlobalSearchScope.allScope(project)); + } + static boolean isJavaLangClass(@Nullable PsiClass aClass) { return aClass != null && CommonClassNames.JAVA_LANG_CLASS.equals(aClass.getQualifiedName()); } @@ -209,4 +220,87 @@ class JavaReflectionReferenceUtil { context.commitDocument(); shortenArgumentsClassReferences(context); } + + public static class ReflectiveType { + final PsiClass myPsiClass; + final PsiPrimitiveType myPrimitiveType; + final int myArrayDimensions; + + public ReflectiveType(PsiClass psiClass, PsiPrimitiveType primitiveType, int arrayDimensions) { + myPsiClass = psiClass; + myPrimitiveType = primitiveType; + myArrayDimensions = arrayDimensions; + } + + @Nullable + public String getQualifiedName() { + String text = null; + if (myPrimitiveType != null) { + text = myPrimitiveType.getCanonicalText(); + } + else if (myPsiClass != null) { + text = myPsiClass.getQualifiedName(); + } + if (myArrayDimensions == 0 || text == null) { + return text; + } + final StringBuilder sb = new StringBuilder(text); + for (int i = 0; i < myArrayDimensions; i++) { + sb.append("[]"); + } + return sb.toString(); + } + + @Override + public String toString() { + final String name = getQualifiedName(); + return name != null ? name : "null"; + } + + public boolean isEqualTo(@Nullable PsiType otherType) { + if (otherType == null || myArrayDimensions != otherType.getArrayDimensions()) { + return false; + } + final PsiType otherComponentType = otherType.getDeepComponentType(); + if (myPrimitiveType != null) { + return myPrimitiveType.equals(otherComponentType); + } + if (myPsiClass != null) { + final PsiClass otherClass = PsiUtil.resolveClassInType(otherComponentType); + if (otherClass != null) { + final String otherClassName = otherClass instanceof PsiTypeParameter + ? CommonClassNames.JAVA_LANG_OBJECT : otherClass.getQualifiedName(); + if (otherClassName != null) { + return otherClassName.equals(myPsiClass.getQualifiedName()); + } + } + } + return false; + } + + @Nullable + public static ReflectiveType create(@Nullable PsiType originalType, @NotNull PsiElement context) { + if (originalType == null) { + return null; + } + final int arrayDimensions = originalType.getArrayDimensions(); + final PsiType type = originalType.getDeepComponentType(); + if (type instanceof PsiPrimitiveType) { + return new ReflectiveType(null, (PsiPrimitiveType)type, arrayDimensions); + } + PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass instanceof PsiTypeParameter) { + psiClass = findClass(CommonClassNames.JAVA_LANG_OBJECT, context); + } + if (psiClass != null) { + return new ReflectiveType(psiClass, null, arrayDimensions); + } + return null; + } + + @Nullable + public static ReflectiveType create(@Nullable PsiClass psiClass) { + return psiClass != null ? new ReflectiveType(psiClass, null, 0) : null; + } + } } diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/Constructor.java b/java/java-tests/testData/inspection/invokeHandleSignature/Constructor.java new file mode 100644 index 000000000000..404a11c958a9 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/Constructor.java @@ -0,0 +1,43 @@ +import java.lang.invoke.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + Class c = Test.class; + l.findConstructor(Test.class, MethodType.methodType(void.class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, int.class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, int.class, String.class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, int.class, String[].class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, String[][].class)); + l.findConstructor((c), MethodType.methodType(void.class)); + + l.findConstructor(Test.class, MethodType.methodType(int.class)); + l.findConstructor(Test.class, MethodType.methodType(Test.class)); + l.findConstructor(Test.class, MethodType.methodType(Test.class, int.class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, String.class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, int.class, String[][].class)); + l.findConstructor(Test.class, MethodType.methodType(void.class, String[].class)); + + l.findConstructor(WithDefault.class, MethodType.methodType(void.class)); + l.findConstructor(Class.forName("WithDefault"), MethodType.methodType(void.class)); + l.findConstructor(NoDefault.class, MethodType.methodType(void.class)); + l.findConstructor(Class.forName("NoDefault"), MethodType.methodType(void.class)); + + } +} + +class Test { + public Test() {} + public Test(int a) {} + public Test(int a, String b) {} + public Test(int a, String... b) {} + public Test(String[]... b) {} +} + +class WithDefault { +} + +class NoDefault { + public NoDefault(int n) {} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/GenericMethod.java b/java/java-tests/testData/inspection/invokeHandleSignature/GenericMethod.java new file mode 100644 index 000000000000..c8492b208a28 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/GenericMethod.java @@ -0,0 +1,25 @@ +import java.lang.invoke.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findVirtual(Test.class, "method1", MethodType.genericMethodType(2)); + l.findVirtual(Test.class, "method1", MethodType.genericMethodType(2, false)); + l.findVirtual(Test.class, "method1", MethodType.genericMethodType(2, true)); + l.findVirtual(Test.class, "method1", MethodType.genericMethodType(1)); + l.findVirtual(Test.class, "method1", MethodType.genericMethodType(3)); + + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(2, true)); + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(2, false)); + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(1)); + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(1, true)); + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(2)); + l.findVirtual(Test.class, "method2", MethodType.genericMethodType(3)); + } +} + +class Test { + public T method1(T a, T b) {return null;} + public T method2(T a, T b, T... c) {return null;} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/Getter.java b/java/java-tests/testData/inspection/invokeHandleSignature/Getter.java new file mode 100644 index 000000000000..56e4a8a9dda7 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/Getter.java @@ -0,0 +1,47 @@ +import java.lang.invoke.*; +import java.util.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findGetter(Test.class, "myInt", int.class); + l.findGetter(Test.class, "myInts", int[].class); + l.findGetter(Test.class, "myList", List.class); + l.findGetter(Test.class, "myLists", List[].class); + l.findGetter(Test.class, "myString", String.class); + + l.findStaticGetter(Test.class, "ourInt", int.class); + l.findStaticGetter(Test.class, "ourInts", int[].class); + l.findStaticGetter(Test.class, "ourList", List.class); + l.findStaticGetter(Test.class, "ourLists", List[].class); + l.findStaticGetter(Test.class, "ourString", String.class); + + l.findGetter(Test.class, "doesntExist", String.class); + l.findStaticGetter(Test.class, "doesntExist", String.class); + + l.findGetter(Test.class, "myInt", void.class); + l.findGetter(Test.class, "myInts", int.class); + l.findGetter(Test.class, "myString", List.class); + + l.findStaticGetter(Test.class, "ourInt", void.class); + l.findStaticGetter(Test.class, "ourInts", int.class); + l.findStaticGetter(Test.class, "ourString", List.class); + } +} + +class Test { + public int myInt; + public String myString; + public int[] myInts; + public List myList; + @SuppressWarnings("unchecked") + public List[] myLists; + + public static int ourInt; + public static String ourString; + public static int[] ourInts; + public static List ourList; + @SuppressWarnings("unchecked") + public static List[] ourLists; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/OverloadedMethod.java b/java/java-tests/testData/inspection/invokeHandleSignature/OverloadedMethod.java new file mode 100644 index 000000000000..71b280985225 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/OverloadedMethod.java @@ -0,0 +1,41 @@ +import java.lang.invoke.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findVirtual(Test.class, "method", MethodType.methodType(void.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, int.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(boolean.class, short.class, char.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, int.class, int[].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, long.class, int[][].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, long.class, int[][][].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(Object.class, Object.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(Object.class, Object[].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(Object.class, Object[][].class)); + l.findVirtual(Test.class, "method", MethodType.genericMethodType(1)); + + l.findVirtual(Test.class, "method", MethodType.methodType(void.class, void.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(void.class, int.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(short.class, char.class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, int[].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(int.class, int[][].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(Object[].class, Object[].class)); + l.findVirtual(Test.class, "method", MethodType.methodType(Object[][].class, Object[][].class)); + + l.findStatic(Test.class, "method", MethodType.methodType(void.class)); + l.findVirtual(Test.class, "doesntExist", MethodType.methodType(void.class)); + } +} + +class Test { + public void method() {} + public int method(int n) {return n;} + public boolean method(short a, char b) {return true;} + public int method(int n, int... a) {return n;} + public int method(long n, int[]... a) {return a.length;} + public int method(long n, int[][][] a) {return a.length;} + public Object method(Object o) {return o;} + public Object method(Object[] o) {return o;} + public Object method(Object[][] o) {return o;} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/Setter.java b/java/java-tests/testData/inspection/invokeHandleSignature/Setter.java new file mode 100644 index 000000000000..570fc69aaca8 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/Setter.java @@ -0,0 +1,47 @@ +import java.lang.invoke.*; +import java.util.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findSetter(Test.class, "myInt", int.class); + l.findSetter(Test.class, "myInts", int[].class); + l.findSetter(Test.class, "myList", List.class); + l.findSetter(Test.class, "myLists", List[].class); + l.findSetter(Test.class, "myString", String.class); + + l.findStaticSetter(Test.class, "ourInt", int.class); + l.findStaticSetter(Test.class, "ourInts", int[].class); + l.findStaticSetter(Test.class, "ourList", List.class); + l.findStaticSetter(Test.class, "ourLists", List[].class); + l.findStaticSetter(Test.class, "ourString", String.class); + + l.findSetter(Test.class, "doesntExist", String.class); + l.findStaticSetter(Test.class, "doesntExist", String.class); + + l.findSetter(Test.class, "myInt", void.class); + l.findSetter(Test.class, "myInts", int.class); + l.findSetter(Test.class, "myString", List.class); + + l.findStaticSetter(Test.class, "ourInt", void.class); + l.findStaticSetter(Test.class, "ourInts", int.class); + l.findStaticSetter(Test.class, "ourString", List.class); + } +} + +class Test { + public int myInt; + public String myString; + public int[] myInts; + public List myList; + @SuppressWarnings("unchecked") + public List[] myLists; + + public static int ourInt; + public static String ourString; + public static int[] ourInts; + public static List ourList; + @SuppressWarnings("unchecked") + public static List[] ourLists; +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/StaticMethod.java b/java/java-tests/testData/inspection/invokeHandleSignature/StaticMethod.java new file mode 100644 index 000000000000..0caa77cd9035 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/StaticMethod.java @@ -0,0 +1,24 @@ +import java.lang.invoke.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findStatic(Test.class, "method1", MethodType.methodType(void.class)); + l.findStatic(Test.class, "method2", MethodType.methodType(String.class, String.class)); + l.findStatic(Test.class, "method3", MethodType.methodType(String.class, String.class, String[].class)); + + l.findStatic(Test.class, "method1", MethodType.methodType(Test.class)); + l.findStatic(Test.class, "method2", MethodType.methodType(int.class, String.class)); + l.findStatic(Test.class, "method3", MethodType.methodType(String.class)); + + l.findVirtual(Test.class, "method1", MethodType.methodType(void.class)); + l.findStatic(Test.class, "doesntExist", MethodType.methodType(String.class)); + } +} + +class Test { + public static void method1() {} + public static String method2(String a) {return a;} + public static String method3(String a, String... b) {return a;} +} \ No newline at end of file diff --git a/java/java-tests/testData/inspection/invokeHandleSignature/VarHandle.java b/java/java-tests/testData/inspection/invokeHandleSignature/VarHandle.java new file mode 100644 index 000000000000..15cdf0c5db08 --- /dev/null +++ b/java/java-tests/testData/inspection/invokeHandleSignature/VarHandle.java @@ -0,0 +1,47 @@ +import java.lang.invoke.*; +import java.util.*; + +class Main { + void foo() throws Exception { + MethodHandles.Lookup l = MethodHandles.lookup(); + + l.findVarHandle(Test.class, "myInt", int.class); + l.findVarHandle(Test.class, "myInts", int[].class); + l.findVarHandle(Test.class, "myList", List.class); + l.findVarHandle(Test.class, "myLists", List[].class); + l.findVarHandle(Test.class, "myString", String.class); + + l.findStaticVarHandle(Test.class, "ourInt", int.class); + l.findStaticVarHandle(Test.class, "ourInts", int[].class); + l.findStaticVarHandle(Test.class, "ourList", List.class); + l.findStaticVarHandle(Test.class, "ourLists", List[].class); + l.findStaticVarHandle(Test.class, "ourString", String.class); + + l.findVarHandle(Test.class, "doesntExist", String.class); + l.findStaticVarHandle(Test.class, "doesntExist", String.class); + + l.findVarHandle(Test.class, "myInt", void.class); + l.findVarHandle(Test.class, "myInts", int.class); + l.findVarHandle(Test.class, "myString", List.class); + + l.findStaticVarHandle(Test.class, "ourInt", void.class); + l.findStaticVarHandle(Test.class, "ourInts", int.class); + l.findStaticVarHandle(Test.class, "ourString", List.class); + } +} + +class Test { + public int myInt; + public String myString; + public int[] myInts; + public List myList; + @SuppressWarnings("unchecked") + public List[] myLists; + + public static int ourInt; + public static String ourString; + public static int[] ourInts; + public static List ourList; + @SuppressWarnings("unchecked") + public static List[] ourLists; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/JavaLangInvokeHandleSignatureTest.kt b/java/java-tests/testSrc/com/intellij/codeInspection/JavaLangInvokeHandleSignatureTest.kt new file mode 100644 index 000000000000..8b571101f658 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/codeInspection/JavaLangInvokeHandleSignatureTest.kt @@ -0,0 +1,56 @@ +/* + * 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.JavaLangInvokeHandleSignatureInspection +import com.intellij.testFramework.LightProjectDescriptor +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase + +/** + * @author Pavel.Dolgov + */ +class JavaLangInvokeHandleSignatureTest : LightCodeInsightFixtureTestCase() { + + override fun getBasePath() = JavaTestUtil.getRelativeJavaTestDataPath() + "/inspection/invokeHandleSignature" + + override fun getProjectDescriptor(): LightProjectDescriptor { + return LightCodeInsightFixtureTestCase.JAVA_9 + } + + override fun setUp() { + super.setUp() + myFixture.enableInspections(JavaLangInvokeHandleSignatureInspection()) + } + + fun testGenericMethod() = doTest() + + fun testOverloadedMethod() = doTest() + + fun testStaticMethod() = doTest() + + fun testConstructor() = doTest() + + fun testVarHandle() = doTest() + + fun testGetter() = doTest() + + fun testSetter() = doTest() + + private fun doTest() { + myFixture.testHighlighting(getTestName(false) + ".java") + } +} diff --git a/platform/platform-resources-en/src/messages/InspectionsBundle.properties b/platform/platform-resources-en/src/messages/InspectionsBundle.properties index f938f477b5d3..2c8b4a6b095b 100644 --- a/platform/platform-resources-en/src/messages/InspectionsBundle.properties +++ b/platform/platform-resources-en/src/messages/InspectionsBundle.properties @@ -557,6 +557,7 @@ group.names.javaee.issues=Java EE issues group.names.properties.files=Properties Files group.names.xml=XML group.names.toString.issues=toString() issues +group.names.reflective.access.issues=Reflective access issues duplicate.property.display.name=Duplicate Property @@ -798,3 +799,17 @@ inspection.null.value.for.optional.context.parameter=parameter inspection.null.value.for.optional.context.lambda=lambda expression inspection.null.value.for.optional.context.return=return statement inspection.null.value.for.optional.context.declaration=declaration + +inspection.handle.signature.name=MethodHandle/VarHandle type mismatch +inspection.handle.signature.field.static=Field ''{0}'' is static +inspection.handle.signature.field.not.static=Field ''{0}'' is not static +inspection.handle.signature.field.type=The type of field ''{0}'' is ''{1}'' +inspection.handle.signature.field.cannot.resolve=Cannot resolve field ''{0}'' +inspection.handle.signature.method.static=Method ''{0}'' is static +inspection.handle.signature.method.not.static=Method ''{0}'' is not static + +inspection.handle.signature.change.type.fix.name=Change type to ''{0}'' +inspection.handle.signature.replace.with.fix.name=Replace with ''{0}'' +inspection.handle.signature.use.method.fix.name=Use method ''{0}'' +inspection.handle.signature.use.constructor.fix.name=Use constructor ''{0}'' + diff --git a/resources-en/src/inspectionDescriptions/JavaLangInvokeHandleSignature.html b/resources-en/src/inspectionDescriptions/JavaLangInvokeHandleSignature.html new file mode 100644 index 000000000000..ce228fe2bf23 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/JavaLangInvokeHandleSignature.html @@ -0,0 +1,6 @@ + + +This inspection detects the case where the type of a VarHandle or the signature of a MethodHandle doesn't match the actual field or method. +

It also detects if a static field/method is accessed in non-static way and vice versa. + + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 498f0adae99a..a836d1cb3c8e 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -923,6 +923,10 @@ groupPath="Java" groupBundle="messages.InspectionsBundle" groupKey="group.names.naming.conventions" bundle="messages.InspectionsBundle" key="inspection.java.module.naming" implementationClass="com.intellij.codeInspection.java19modules.JavaModuleNamingInspection"/> + com.intellij.codeInsight.intention.impl.SplitIfAction