mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Java: Implemented "MethodHandle/VarHandle type mismatch" inspection (IDEA-167318)
This commit is contained in:
+494
@@ -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<String> KNOWN_METHOD_NAMES = Collections.unmodifiableSet(
|
||||
ContainerUtil.union(Arrays.asList(HANDLE_FACTORY_METHOD_NAMES), Collections.singletonList(FIND_CONSTRUCTOR)));
|
||||
|
||||
private static final List<String> 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<String> methodSignature = extractMethodSignature(typeExpression);
|
||||
if (methodSignature != null) {
|
||||
final List<PsiMethod> 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<PsiMethod> 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<String> 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<String> 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<String> 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<PsiMethod> methods, @NotNull List<String> 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<String> 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<String> extractMethodSignature(PsiExpression[] arguments) {
|
||||
final List<String> typeNames = Arrays.stream(arguments)
|
||||
.map(JavaLangInvokeHandleSignatureInspection::getTypeText)
|
||||
.collect(Collectors.toList());
|
||||
return !typeNames.isEmpty() && !typeNames.contains(null) ? typeNames : null;
|
||||
}
|
||||
|
||||
private static List<String> 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<String> 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<String> extractMethodSignature(@Nullable PsiMethod method) {
|
||||
if (method != null) {
|
||||
final List<String> 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<String, String> STATIC_TO_NON_STATIC = ContainerUtil.<String, String>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<String, String> NON_STATIC_TO_STATIC = ContainerUtil.<String, String>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<String> myMethodSignature;
|
||||
|
||||
public ReplaceSignatureQuickFix(@NotNull String fixName, @NotNull List<String> 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<String> 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<String> methodSignature = extractMethodSignature(constructor);
|
||||
return methodSignature != null ? createConstructorSignatureFix(ownerClass, methodSignature) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static LocalQuickFix createConstructorSignatureFix(@NotNull PsiClass ownerClass, @NotNull List<String> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
-11
@@ -44,20 +44,26 @@ import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflection
|
||||
* @author Pavel.Dolgov
|
||||
*/
|
||||
public class JavaLangInvokeHandleReference extends PsiReferenceBase<PsiLiteralExpression> implements InsertHandler<LookupElement> {
|
||||
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;
|
||||
|
||||
|
||||
+1
-4
@@ -48,10 +48,7 @@ public class JavaReflectionReferenceContributor extends PsiReferenceContributor
|
||||
|
||||
private static final ElementPattern<? extends PsiElement> 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
|
||||
|
||||
+116
-22
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, <warning descr="Cannot resolve constructor 'int Test()'">MethodType.methodType(int.class)</warning>);
|
||||
l.findConstructor(Test.class, <warning descr="Cannot resolve constructor 'Test Test()'">MethodType.methodType(Test.class)</warning>);
|
||||
l.findConstructor(Test.class, <warning descr="Cannot resolve constructor 'Test Test(int)'">MethodType.methodType(Test.class, int.class)</warning>);
|
||||
l.findConstructor(Test.class, <warning descr="Cannot resolve constructor 'Test(java.lang.String)'">MethodType.methodType(void.class, String.class)</warning>);
|
||||
l.findConstructor(Test.class, <warning descr="Cannot resolve constructor 'Test(int, java.lang.String[][])'">MethodType.methodType(void.class, int.class, String[][].class)</warning>);
|
||||
l.findConstructor(Test.class, <warning descr="Cannot resolve constructor 'Test(java.lang.String[])'">MethodType.methodType(void.class, String[].class)</warning>);
|
||||
|
||||
l.findConstructor(WithDefault.class, MethodType.methodType(void.class));
|
||||
l.findConstructor(Class.forName("WithDefault"), MethodType.methodType(void.class));
|
||||
l.findConstructor(NoDefault.class, <warning descr="Cannot resolve constructor 'NoDefault()'">MethodType.methodType(void.class)</warning>);
|
||||
l.findConstructor(Class.forName("NoDefault"), <warning descr="Cannot resolve constructor 'NoDefault()'">MethodType.methodType(void.class)</warning>);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
@@ -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", <warning descr="Cannot resolve method 'java.lang.Object method1(java.lang.Object, java.lang.Object, java.lang.Object[])'">MethodType.genericMethodType(2, true)</warning>);
|
||||
l.findVirtual(Test.class, "method1", <warning descr="Cannot resolve method 'java.lang.Object method1(java.lang.Object)'">MethodType.genericMethodType(1)</warning>);
|
||||
l.findVirtual(Test.class, "method1", <warning descr="Cannot resolve method 'java.lang.Object method1(java.lang.Object, java.lang.Object, java.lang.Object)'">MethodType.genericMethodType(3)</warning>);
|
||||
|
||||
l.findVirtual(Test.class, "method2", MethodType.genericMethodType(2, true));
|
||||
l.findVirtual(Test.class, "method2", <warning descr="Cannot resolve method 'java.lang.Object method2(java.lang.Object, java.lang.Object)'">MethodType.genericMethodType(2, false)</warning>);
|
||||
l.findVirtual(Test.class, "method2", <warning descr="Cannot resolve method 'java.lang.Object method2(java.lang.Object)'">MethodType.genericMethodType(1)</warning>);
|
||||
l.findVirtual(Test.class, "method2", <warning descr="Cannot resolve method 'java.lang.Object method2(java.lang.Object, java.lang.Object[])'">MethodType.genericMethodType(1, true)</warning>);
|
||||
l.findVirtual(Test.class, "method2", <warning descr="Cannot resolve method 'java.lang.Object method2(java.lang.Object, java.lang.Object)'">MethodType.genericMethodType(2)</warning>);
|
||||
l.findVirtual(Test.class, "method2", <warning descr="Cannot resolve method 'java.lang.Object method2(java.lang.Object, java.lang.Object, java.lang.Object)'">MethodType.genericMethodType(3)</warning>);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
public <T> T method1(T a, T b) {return null;}
|
||||
public <T> T method2(T a, T b, T... c) {return null;}
|
||||
}
|
||||
@@ -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, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
l.findStaticGetter(Test.class, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
|
||||
l.findGetter(Test.class, "myInt", <warning descr="The type of field 'myInt' is 'int'">void.class</warning>);
|
||||
l.findGetter(Test.class, "myInts", <warning descr="The type of field 'myInts' is 'int[]'">int.class</warning>);
|
||||
l.findGetter(Test.class, "myString", <warning descr="The type of field 'myString' is 'java.lang.String'">List.class</warning>);
|
||||
|
||||
l.findStaticGetter(Test.class, "ourInt", <warning descr="The type of field 'ourInt' is 'int'">void.class</warning>);
|
||||
l.findStaticGetter(Test.class, "ourInts", <warning descr="The type of field 'ourInts' is 'int[]'">int.class</warning>);
|
||||
l.findStaticGetter(Test.class, "ourString", <warning descr="The type of field 'ourString' is 'java.lang.String'">List.class</warning>);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
public int myInt;
|
||||
public String myString;
|
||||
public int[] myInts;
|
||||
public List<String> myList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String>[] myLists;
|
||||
|
||||
public static int ourInt;
|
||||
public static String ourString;
|
||||
public static int[] ourInts;
|
||||
public static List<String> ourList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String>[] ourLists;
|
||||
}
|
||||
@@ -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", <warning descr="Cannot resolve method 'void method(void)'">MethodType.methodType(void.class, void.class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'void method(int)'">MethodType.methodType(void.class, int.class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'short method(char)'">MethodType.methodType(short.class, char.class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'int method(int[])'">MethodType.methodType(int.class, int[].class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'int method(int[][])'">MethodType.methodType(int.class, int[][].class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'java.lang.Object[] method(java.lang.Object[])'">MethodType.methodType(Object[].class, Object[].class)</warning>);
|
||||
l.findVirtual(Test.class, "method", <warning descr="Cannot resolve method 'java.lang.Object[][] method(java.lang.Object[][])'">MethodType.methodType(Object[][].class, Object[][].class)</warning>);
|
||||
|
||||
l.<warning descr="Method 'method' is static">findStatic</warning>(Test.class, "method", MethodType.methodType(void.class));
|
||||
l.findVirtual(Test.class, <warning descr="Cannot resolve method 'doesntExist'">"doesntExist"</warning>, 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;}
|
||||
}
|
||||
@@ -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, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
l.findStaticSetter(Test.class, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
|
||||
l.findSetter(Test.class, "myInt", <warning descr="The type of field 'myInt' is 'int'">void.class</warning>);
|
||||
l.findSetter(Test.class, "myInts", <warning descr="The type of field 'myInts' is 'int[]'">int.class</warning>);
|
||||
l.findSetter(Test.class, "myString", <warning descr="The type of field 'myString' is 'java.lang.String'">List.class</warning>);
|
||||
|
||||
l.findStaticSetter(Test.class, "ourInt", <warning descr="The type of field 'ourInt' is 'int'">void.class</warning>);
|
||||
l.findStaticSetter(Test.class, "ourInts", <warning descr="The type of field 'ourInts' is 'int[]'">int.class</warning>);
|
||||
l.findStaticSetter(Test.class, "ourString", <warning descr="The type of field 'ourString' is 'java.lang.String'">List.class</warning>);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
public int myInt;
|
||||
public String myString;
|
||||
public int[] myInts;
|
||||
public List<String> myList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String>[] myLists;
|
||||
|
||||
public static int ourInt;
|
||||
public static String ourString;
|
||||
public static int[] ourInts;
|
||||
public static List<String> ourList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String>[] ourLists;
|
||||
}
|
||||
@@ -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", <warning descr="Cannot resolve method 'Test method1()'">MethodType.methodType(Test.class)</warning>);
|
||||
l.findStatic(Test.class, "method2", <warning descr="Cannot resolve method 'int method2(java.lang.String)'">MethodType.methodType(int.class, String.class)</warning>);
|
||||
l.findStatic(Test.class, "method3", <warning descr="Cannot resolve method 'java.lang.String method3()'">MethodType.methodType(String.class)</warning>);
|
||||
|
||||
l.<warning descr="Method 'method1' is not static">findVirtual</warning>(Test.class, "method1", MethodType.methodType(void.class));
|
||||
l.findStatic(Test.class, <warning descr="Cannot resolve method 'doesntExist'">"doesntExist"</warning>, 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;}
|
||||
}
|
||||
@@ -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, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
l.findStaticVarHandle(Test.class, <warning descr="Cannot resolve field 'doesntExist'">"doesntExist"</warning>, String.class);
|
||||
|
||||
l.findVarHandle(Test.class, "myInt", <warning descr="The type of field 'myInt' is 'int'">void.class</warning>);
|
||||
l.findVarHandle(Test.class, "myInts", <warning descr="The type of field 'myInts' is 'int[]'">int.class</warning>);
|
||||
l.findVarHandle(Test.class, "myString", <warning descr="The type of field 'myString' is 'java.lang.String'">List.class</warning>);
|
||||
|
||||
l.findStaticVarHandle(Test.class, "ourInt", <warning descr="The type of field 'ourInt' is 'int'">void.class</warning>);
|
||||
l.findStaticVarHandle(Test.class, "ourInts", <warning descr="The type of field 'ourInts' is 'int[]'">int.class</warning>);
|
||||
l.findStaticVarHandle(Test.class, "ourString", <warning descr="The type of field 'ourString' is 'java.lang.String'">List.class</warning>);
|
||||
}
|
||||
}
|
||||
|
||||
class Test {
|
||||
public int myInt;
|
||||
public String myString;
|
||||
public int[] myInts;
|
||||
public List<String> myList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String>[] myLists;
|
||||
|
||||
public static int ourInt;
|
||||
public static String ourString;
|
||||
public static int[] ourInts;
|
||||
public static List<String> ourList;
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<String>[] ourLists;
|
||||
}
|
||||
+56
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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}''
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
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.
|
||||
<p>It also detects if a static field/method is accessed in non-static way and vice versa.
|
||||
</body>
|
||||
</html>
|
||||
@@ -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"/>
|
||||
<localInspection language="JAVA" shortName="JavaLangInvokeHandleSignature" enabledByDefault="true" level="WARNING"
|
||||
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"/>
|
||||
|
||||
<intentionAction>
|
||||
<className>com.intellij.codeInsight.intention.impl.SplitIfAction</className>
|
||||
|
||||
Reference in New Issue
Block a user