mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-18 09:34:34 +07:00
cleanup IDEA-382376: move com.siyeh.ig.bugs package from java-impl to java-impl-inspections
GitOrigin-RevId: 0d2e3640f64ba91bcc6869a430e45e52d01a2e2e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b7ee3ca172
commit
b924d3dcc4
+174
@@ -0,0 +1,174 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.psi.PsiAssignmentExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiThisExpression;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.JavaPsiConstructorUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class CopyConstructorMissesFieldInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
protected @NotNull String buildErrorString(Object... infos) {
|
||||
final List<PsiField> fields = (List<PsiField>)infos[0];
|
||||
if (fields.size() == 1) {
|
||||
return InspectionGadgetsBundle.message("copy.constructor.misses.field.problem.descriptor.1", fields.get(0).getName());
|
||||
}
|
||||
else if (fields.size() == 2) {
|
||||
return InspectionGadgetsBundle.message("copy.constructor.misses.field.problem.descriptor.2",
|
||||
fields.get(0).getName(), fields.get(1).getName());
|
||||
}
|
||||
else if (fields.size() == 3) {
|
||||
return InspectionGadgetsBundle.message("copy.constructor.misses.field.problem.descriptor.3",
|
||||
fields.get(0).getName(), fields.get(1).getName(), fields.get(2).getName());
|
||||
}
|
||||
return InspectionGadgetsBundle.message("copy.constructor.misses.field.problem.descriptor.many", fields.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new CopyConstructorMissesFieldVisitor();
|
||||
}
|
||||
|
||||
private static class CopyConstructorMissesFieldVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
if (!MethodUtils.isCopyConstructor(method)) {
|
||||
return;
|
||||
}
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) {
|
||||
return;
|
||||
}
|
||||
final List<PsiField> fields = new ArrayList<>(ContainerUtil.filter(aClass.getFields(),
|
||||
f -> !f.hasModifierProperty(PsiModifier.STATIC) &&
|
||||
!f.hasModifierProperty(PsiModifier.TRANSIENT) &&
|
||||
(!f.hasModifierProperty(PsiModifier.FINAL) || f.getInitializer() == null)));
|
||||
if (fields.isEmpty()) return;
|
||||
final PsiParameter parameter = Objects.requireNonNull(method.getParameterList().getParameter(0));
|
||||
final List<PsiField> assignedFields = new SmartList<>();
|
||||
final Set<PsiMethod> methodsOneLevelDeep = new HashSet<>();
|
||||
if (!PsiTreeUtil.processElements(method, e -> collectAssignedFields(e, parameter, methodsOneLevelDeep, assignedFields))) {
|
||||
return;
|
||||
}
|
||||
if (aClass.isRecord() && ContainerUtil.exists(methodsOneLevelDeep, m -> JavaPsiRecordUtil.isCanonicalConstructor(m))) {
|
||||
return;
|
||||
}
|
||||
for (PsiMethod calledMethod : methodsOneLevelDeep) {
|
||||
if (!PsiTreeUtil.processElements(calledMethod, e -> collectAssignedFields(e, null, null, assignedFields))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (PsiField assignedField : assignedFields) {
|
||||
if (aClass == PsiUtil.resolveClassInClassTypeOnly(assignedField.getType())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
fields.removeAll(assignedFields);
|
||||
if (fields.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
registerMethodError(method, fields);
|
||||
}
|
||||
|
||||
private static boolean collectAssignedFields(PsiElement element, PsiParameter parameter,
|
||||
@Nullable Set<? super PsiMethod> methods, List<? super PsiField> assignedFields) {
|
||||
if (element instanceof PsiAssignmentExpression) {
|
||||
final PsiExpression lhs = PsiUtil.skipParenthesizedExprDown(((PsiAssignmentExpression)element).getLExpression());
|
||||
final PsiVariable variable = resolveVariable(lhs, null);
|
||||
if (variable instanceof PsiField) {
|
||||
assignedFields.add((PsiField)variable);
|
||||
}
|
||||
}
|
||||
else if (JavaPsiConstructorUtil.isChainedConstructorCall(element)) {
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)element;
|
||||
for (PsiExpression argument : methodCallExpression.getArgumentList().getExpressions()) {
|
||||
argument = PsiUtil.skipParenthesizedExprDown(argument);
|
||||
final PsiVariable variable = resolveVariable(argument, parameter);
|
||||
if (variable == parameter) {
|
||||
// instance to copy is passed to another constructor
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (methods != null) {
|
||||
final PsiMethod constructor = methodCallExpression.resolveMethod();
|
||||
if (constructor != null) {
|
||||
methods.add(constructor);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element instanceof PsiMethodCallExpression methodCallExpression) {
|
||||
final PsiExpression qualifier =
|
||||
PsiUtil.skipParenthesizedExprDown(methodCallExpression.getMethodExpression().getQualifierExpression());
|
||||
if (qualifier == null || qualifier instanceof PsiThisExpression) {
|
||||
final PsiMethod method = methodCallExpression.resolveMethod();
|
||||
final PsiField field = PropertyUtil.getFieldOfSetter(method);
|
||||
if (field != null) {
|
||||
// field assigned using setter
|
||||
assignedFields.add(field);
|
||||
}
|
||||
else if (methods != null && method != null) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
else if (qualifier instanceof PsiReferenceExpression referenceExpression) {
|
||||
// consider field assigned if method is called on it.
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
if (target instanceof PsiField) {
|
||||
assignedFields.add((PsiField)target);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static PsiVariable resolveVariable(PsiExpression expression, @Nullable PsiParameter requiredQualifier) {
|
||||
if (!(expression instanceof PsiReferenceExpression referenceExpression)) {
|
||||
return null;
|
||||
}
|
||||
final PsiExpression qualifier = PsiUtil.skipParenthesizedExprDown(referenceExpression.getQualifierExpression());
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
if (requiredQualifier == null) {
|
||||
if (!(qualifier == null || qualifier instanceof PsiThisExpression)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (!ExpressionUtils.isReferenceTo(qualifier, requiredQualifier)) {
|
||||
return target == requiredQualifier ? requiredQualifier : null;
|
||||
}
|
||||
return target instanceof PsiVariable ? (PsiVariable)target : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/*
|
||||
* Copyright 2003-2015 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* 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.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteElementFix;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.DeleteSideEffectsAwareFix;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiDoWhileStatement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiForStatement;
|
||||
import com.intellij.psi.PsiForeachStatement;
|
||||
import com.intellij.psi.PsiIfStatement;
|
||||
import com.intellij.psi.PsiLoopStatement;
|
||||
import com.intellij.psi.PsiStatement;
|
||||
import com.intellij.psi.PsiSwitchStatement;
|
||||
import com.intellij.psi.PsiWhileStatement;
|
||||
import com.intellij.psi.util.FileTypeUtils;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.ControlFlowUtils;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class EmptyStatementBodyInspection extends BaseInspection {
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean m_reportEmptyBlocks = true;
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean commentsAreContent = false;
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
node.addContent(new Element("option").setAttribute("name", "m_reportEmptyBlocks").setAttribute("value", String.valueOf(m_reportEmptyBlocks)));
|
||||
if (commentsAreContent) {
|
||||
node.addContent(new Element("option").setAttribute("name", "commentsAreContent").setAttribute("value", "true"));
|
||||
}
|
||||
}
|
||||
|
||||
@Pattern(VALID_ID_PATTERN)
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "StatementWithEmptyBody";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("statement.with.empty.body.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("m_reportEmptyBlocks", InspectionGadgetsBundle.message("statement.with.empty.body.include.option")),
|
||||
checkbox("commentsAreContent", InspectionGadgetsBundle.message("comments.as.content.option")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldInspect(@NotNull PsiFile file) {
|
||||
return !FileTypeUtils.isInServerPageFile(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
return ObjectUtils.tryCast(ArrayUtil.getFirstElement(infos), LocalQuickFix.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new EmptyStatementVisitor();
|
||||
}
|
||||
|
||||
private class EmptyStatementVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitDoWhileStatement(@NotNull PsiDoWhileStatement statement) {
|
||||
super.visitDoWhileStatement(statement);
|
||||
checkLoopStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitWhileStatement(@NotNull PsiWhileStatement statement) {
|
||||
super.visitWhileStatement(statement);
|
||||
checkLoopStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForStatement(@NotNull PsiForStatement statement) {
|
||||
super.visitForStatement(statement);
|
||||
checkLoopStatement(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitForeachStatement(@NotNull PsiForeachStatement statement) {
|
||||
super.visitForeachStatement(statement);
|
||||
final PsiStatement body = statement.getBody();
|
||||
if (body != null && isEmpty(body)) {
|
||||
registerStatementError(statement, createFix(statement, statement.getIteratedValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkLoopStatement(PsiLoopStatement statement) {
|
||||
final PsiStatement body = statement.getBody();
|
||||
if (body == null || !isEmpty(body)) {
|
||||
return;
|
||||
}
|
||||
registerStatementError(statement);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitIfStatement(@NotNull PsiIfStatement statement) {
|
||||
super.visitIfStatement(statement);
|
||||
final PsiStatement thenBranch = statement.getThenBranch();
|
||||
final PsiStatement elseBranch = statement.getElseBranch();
|
||||
if (thenBranch != null && isEmpty(thenBranch)) {
|
||||
LocalQuickFix fix = elseBranch == null || isEmpty(elseBranch) ? createFix(statement, statement.getCondition()) : null;
|
||||
registerStatementError(statement, fix);
|
||||
return;
|
||||
}
|
||||
if (elseBranch != null && isEmpty(elseBranch)) {
|
||||
final PsiElement elseToken = statement.getElseElement();
|
||||
if (elseToken == null) {
|
||||
return;
|
||||
}
|
||||
registerError(elseToken, new DeleteElementFix(elseBranch));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitSwitchStatement(@NotNull PsiSwitchStatement statement) {
|
||||
super.visitSwitchStatement(statement);
|
||||
final PsiCodeBlock body = statement.getBody();
|
||||
if (body == null || !isEmpty(body)) {
|
||||
return;
|
||||
}
|
||||
registerStatementError(statement, createFix(statement, statement.getExpression()));
|
||||
}
|
||||
|
||||
private static @NotNull LocalQuickFix createFix(@NotNull PsiStatement statement, PsiExpression expression) {
|
||||
if (expression == null) {
|
||||
return LocalQuickFix.from(new DeleteElementFix(statement));
|
||||
}
|
||||
return LocalQuickFix.from(new DeleteSideEffectsAwareFix(statement, expression));
|
||||
}
|
||||
|
||||
private boolean isEmpty(PsiElement element) {
|
||||
return ControlFlowUtils.isEmpty(element, commentsAreContent, m_reportEmptyBlocks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright 2003-2025 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* 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.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.codeInspection.dataFlow.MethodContract;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaPsiFacade;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiCallExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCompiledElement;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiFunctionalExpression;
|
||||
import com.intellij.psi.PsiLambdaExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiMethodReferenceExpression;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiTryStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.callMatcher.CallMapper;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.junit.JUnitCommonClassNames;
|
||||
import com.siyeh.ig.psiutils.ExceptionUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.LibraryUtil;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import com.siyeh.ig.psiutils.MethodMatcher;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import com.siyeh.ig.psiutils.SideEffectChecker;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.psi.CommonClassNames.JAVA_UTIL_FUNCTION_SUPPLIER;
|
||||
|
||||
public final class IgnoreResultOfCallInspection extends BaseInspection {
|
||||
private static final CallMatcher STREAM_COLLECT =
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_STREAM_STREAM, "collect").parameterCount(1);
|
||||
private static final CallMatcher COLLECTOR_TO_COLLECTION =
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_UTIL_STREAM_COLLECTORS, "toCollection").parameterCount(1);
|
||||
|
||||
private static final CallMatcher KNOWN_ARGUMENT_SIDE_EFFECTS = CallMatcher.anyOf(
|
||||
(CallMatcher.instanceCall( "java.nio.channels.FileChannel","write")));
|
||||
|
||||
private static final CallMapper<String> KNOWN_EXCEPTIONAL_SIDE_EFFECTS = new CallMapper<String>()
|
||||
.register(CallMatcher.staticCall("java.util.regex.Pattern", "compile"), "java.util.regex.PatternSyntaxException")
|
||||
.register(CallMatcher.anyOf(
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_SHORT, "parseShort", "valueOf", "decode"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_BYTE, "parseByte", "valueOf", "decode"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "parseInt", "valueOf", "decode"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "parseLong", "valueOf", "decode"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_DOUBLE, "parseDouble", "valueOf"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_LANG_FLOAT, "parseFloat", "valueOf")), "java.lang.NumberFormatException")
|
||||
.register(CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_CLASS,
|
||||
"getMethod", "getDeclaredMethod", "getConstructor", "getDeclaredConstructor"),
|
||||
"java.lang.NoSuchMethodException")
|
||||
.register(CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_CLASS,
|
||||
"getField", "getDeclaredField"), "java.lang.NoSuchFieldException")
|
||||
.register(CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall("java.time.format.DateTimeFormatter", "parse", "parseBest"),
|
||||
CallMatcher.staticCall("java.time.Duration", "parse"),
|
||||
CallMatcher.staticCall("java.time.Instant", "parse"),
|
||||
CallMatcher.staticCall("java.time.MonthDay", "parse"),
|
||||
CallMatcher.staticCall("java.time.Period", "parse"),
|
||||
CallMatcher.staticCall("java.time.Year", "parse"),
|
||||
CallMatcher.staticCall("java.time.YearMonth", "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_OFFSET_TIME, "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_OFFSET_DATE_TIME, "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_ZONED_DATE_TIME, "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_LOCAL_DATE, "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_LOCAL_DATE_TIME, "parse"),
|
||||
CallMatcher.staticCall(CommonClassNames.JAVA_TIME_LOCAL_TIME, "parse")), "java.time.format.DateTimeParseException")
|
||||
.register(CallMatcher.staticCall("java.util.UUID", "fromString"), "java.lang.IllegalArgumentException");
|
||||
private static final CallMatcher MOCK_LIBS_EXCLUDED_QUALIFIER_CALLS =
|
||||
CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall("org.mockito.stubbing.Stubber", "when"),
|
||||
CallMatcher.staticCall("org.mockito.Mockito", "verify"),
|
||||
CallMatcher.instanceCall("org.jmock.Expectations", "allowing", "ignoring", "never", "one", "oneOf", "with")
|
||||
.parameterTypes("T"),
|
||||
//new version of jmock
|
||||
CallMatcher.instanceCall("org.jmock.AbstractExpectations", "allowing", "ignoring", "never", "one", "oneOf", "with")
|
||||
.parameterTypes("T"),
|
||||
CallMatcher.instanceCall("org.jmock.syntax.ReceiverClause", "of").parameterTypes("T"));
|
||||
|
||||
private static final CallMatcher TEST_OR_MOCK_CONTAINER_METHODS =
|
||||
CallMatcher.anyOf(
|
||||
CallMatcher.staticCall("org.assertj.core.api.Assertions", "assertThatThrownBy", "catchThrowable", "catchThrowableOfType"),
|
||||
CallMatcher.staticCall(JUnitCommonClassNames.ORG_JUNIT_JUPITER_API_ASSERTIONS, "assertDoesNotThrow", "assertThrows", "assertThrowsExactly"),
|
||||
CallMatcher.staticCall(JUnitCommonClassNames.ORG_JUNIT_ASSERT, "assertThrows"),
|
||||
CallMatcher.instanceCall("org.mockito.MockedStatic", "when", "verify")
|
||||
);
|
||||
|
||||
private static final Set<String> CHECK_ANNOTATIONS = Set.of(
|
||||
"javax.annotation.CheckReturnValue",
|
||||
"org.assertj.core.util.CheckReturnValue",
|
||||
"com.google.errorprone.annotations.CheckReturnValue",
|
||||
"org.jetbrains.annotations.CheckReturnValue",
|
||||
"org.springframework.lang.CheckReturnValue");
|
||||
private final MethodMatcher myMethodMatcher;
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public boolean m_reportAllNonLibraryCalls = false;
|
||||
|
||||
public IgnoreResultOfCallInspection() {
|
||||
myMethodMatcher = new MethodMatcher(true, "callCheckString")
|
||||
.add(CommonClassNames.JAVA_IO_FILE, ".*")
|
||||
.add("java.io.InputStream","read|skip|available|markSupported")
|
||||
.add("java.io.Reader","read|skip|ready|markSupported")
|
||||
.add(CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER, "capacity|codePointAt|codePointBefore|codePointCount|indexOf|lastIndexOf|offsetByCodePoints|substring|subSequence")
|
||||
.add(CommonClassNames.JAVA_LANG_BOOLEAN,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_BYTE,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_CHARACTER,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_DOUBLE,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_FLOAT,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_INTEGER,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_LONG,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_MATH,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_OBJECT,"equals|hashCode|toString")
|
||||
.add(CommonClassNames.JAVA_LANG_SHORT,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_STRICT_MATH,".*")
|
||||
.add(CommonClassNames.JAVA_LANG_STRING,".*")
|
||||
.add("java.lang.Thread", "interrupted")
|
||||
.add("java.math.BigDecimal",".*")
|
||||
.add("java.math.BigInteger",".*")
|
||||
.add("java.net.InetAddress",".*")
|
||||
.add(CommonClassNames.JAVA_NET_URI,".*")
|
||||
.add("java.nio.channels.AsynchronousChannelGroup",".*")
|
||||
.add("java.nio.channels.Channel","isOpen")
|
||||
.add("java.nio.channels.FileChannel","open|map|lock|tryLock|write")
|
||||
.add("java.nio.channels.ScatteringByteChannel","read")
|
||||
.add("java.nio.channels.SocketChannel","open|socket|isConnected|isConnectionPending")
|
||||
.add(CommonClassNames.JAVA_UTIL_ARRAYS, ".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_COLLECTIONS, "(?!addAll).*")
|
||||
.add(CommonClassNames.JAVA_UTIL_LIST, "of")
|
||||
.add(CommonClassNames.JAVA_UTIL_MAP, "of|ofEntries|entry")
|
||||
.add(CommonClassNames.JAVA_UTIL_SET, "of")
|
||||
.add(CommonClassNames.JAVA_UTIL_UUID,".*")
|
||||
.add("java.util.concurrent.BlockingQueue", "offer|remove")
|
||||
.add("java.util.concurrent.CountDownLatch","await|getCount")
|
||||
.add("java.util.concurrent.ExecutorService","awaitTermination|isShutdown|isTerminated")
|
||||
.add("java.util.concurrent.ForkJoinPool","awaitQuiescence")
|
||||
.add("java.util.concurrent.Semaphore","tryAcquire|availablePermits|isFair|hasQueuedThreads|getQueueLength|getQueuedThreads")
|
||||
.add("java.util.concurrent.locks.Condition","await|awaitNanos|awaitUntil")
|
||||
.add("java.util.concurrent.locks.Lock","tryLock|newCondition")
|
||||
.add("java.util.regex.Matcher","pattern|toMatchResult|start|end|group|groupCount|matches|find|lookingAt|quoteReplacement|replaceAll|replaceFirst|regionStart|regionEnd|hasTransparentBounds|hasAnchoringBounds|hitEnd|requireEnd")
|
||||
.add("java.util.regex.Pattern",".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM,".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM,".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM,".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM,".*")
|
||||
.add(CommonClassNames.JAVA_UTIL_STREAM_STREAM,".*")
|
||||
.finishDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
myMethodMatcher.getTable("").prefix("myMethodMatcher"),
|
||||
checkbox("m_reportAllNonLibraryCalls", InspectionGadgetsBundle.message("result.of.method.call.ignored.non.library.option"))
|
||||
);
|
||||
}
|
||||
|
||||
@Pattern(VALID_ID_PATTERN)
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "ResultOfMethodCallIgnored";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
final PsiClass containingClass = (PsiClass)infos[0];
|
||||
final String className = containingClass.getName();
|
||||
return InspectionGadgetsBundle.message("result.of.method.call.ignored.problem.descriptor", className);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element element) throws InvalidDataException {
|
||||
super.readSettings(element);
|
||||
myMethodMatcher.readSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element element) throws WriteExternalException {
|
||||
super.writeSettings(element);
|
||||
myMethodMatcher.writeSettings(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new IgnoreResultOfCallVisitor();
|
||||
}
|
||||
|
||||
private class IgnoreResultOfCallVisitor extends BaseInspectionVisitor {
|
||||
@Override
|
||||
public void visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) {
|
||||
if (PsiTypes.voidType().equals(LambdaUtil.getFunctionalInterfaceReturnType(expression)) &&
|
||||
expression.resolve() instanceof PsiMethod method && !method.isConstructor() && shouldReport(expression, method, null)) {
|
||||
registerError(ObjectUtils.notNull(expression.getReferenceNameElement(), expression), method.getContainingClass());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
if (!ExpressionUtils.isVoidContext(expression)) return;
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method != null && !method.isConstructor() && shouldReport(expression, method, expression.getParent())) {
|
||||
registerMethodCallError(expression, method.getContainingClass());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldReport(PsiExpression expression, PsiMethod method, @Nullable PsiElement errorContainer) {
|
||||
final PsiType returnType = method.getReturnType();
|
||||
if (PsiTypes.voidType().equals(returnType) || TypeUtils.typeEquals(CommonClassNames.JAVA_LANG_VOID, returnType)) return false;
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
if (errorContainer != null && PsiUtilCore.hasErrorElementChild(errorContainer)) return false;
|
||||
if (expression instanceof PsiMethodCallExpression call) {
|
||||
final PsiMethodCallExpression previousCall = MethodCallUtils.getQualifierMethodCall(call);
|
||||
if (MOCK_LIBS_EXCLUDED_QUALIFIER_CALLS.test(previousCall)) return false;
|
||||
}
|
||||
if (PropertyUtil.isSimpleGetter(method)) {
|
||||
return !MethodUtils.hasCanIgnoreReturnValueAnnotation(method, null);
|
||||
}
|
||||
if (method instanceof PsiCompiledElement && method.getNavigationElement() instanceof PsiMethod nav &&
|
||||
PropertyUtil.isSimpleGetter(nav)) {
|
||||
return !MethodUtils.hasCanIgnoreReturnValueAnnotation(method, null);
|
||||
}
|
||||
if (isInTestContainer(expression)) return false;
|
||||
if (m_reportAllNonLibraryCalls && !LibraryUtil.classIsInLibrary(aClass)) {
|
||||
return !MethodUtils.hasCanIgnoreReturnValueAnnotation(method, null);
|
||||
}
|
||||
if (isKnownArgumentSideEffect(expression) || isKnownExceptionalSideEffectCaught(expression) || isHardcodedException(expression)) {
|
||||
return false;
|
||||
}
|
||||
if (isPureMethod(method, expression)) {
|
||||
return !MethodUtils.hasCanIgnoreReturnValueAnnotation(method, null);
|
||||
}
|
||||
|
||||
PsiElement stop;
|
||||
if (!myMethodMatcher.matches(method)) {
|
||||
final PsiAnnotation annotation = findCheckReturnValueAnnotation(method);
|
||||
if (annotation == null) return false;
|
||||
stop = (PsiElement)annotation.getOwner();
|
||||
}
|
||||
else {
|
||||
stop = null;
|
||||
}
|
||||
return !MethodUtils.hasCanIgnoreReturnValueAnnotation(method, stop);
|
||||
}
|
||||
|
||||
private static PsiAnnotation findCheckReturnValueAnnotation(PsiMethod method) {
|
||||
final PsiAnnotation annotation = MethodUtils.findAnnotationInTree(method, null, CHECK_ANNOTATIONS);
|
||||
return annotation == null ? getAnnotationByShortNameCheckReturnValue(method) : annotation;
|
||||
}
|
||||
|
||||
private static PsiAnnotation getAnnotationByShortNameCheckReturnValue(PsiMethod method) {
|
||||
for (PsiAnnotation psiAnnotation : method.getAnnotations()) {
|
||||
String qualifiedName = psiAnnotation.getQualifiedName();
|
||||
if (qualifiedName != null && "CheckReturnValue".equals(StringUtil.getShortName(qualifiedName))) {
|
||||
return psiAnnotation;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isKnownArgumentSideEffect(PsiExpression expression) {
|
||||
if (!(expression instanceof PsiMethodCallExpression call) || !KNOWN_ARGUMENT_SIDE_EFFECTS.test(call)) {
|
||||
return false;
|
||||
}
|
||||
PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class);
|
||||
if (method == null) {
|
||||
return false;
|
||||
}
|
||||
PsiExpressionList list = call.getArgumentList();
|
||||
for (PsiExpression argument : list.getExpressions()) {
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNullOrWrapper(argument.getType())) {
|
||||
continue;
|
||||
}
|
||||
if (argument instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiVariable variable) {
|
||||
List<PsiReferenceExpression> references = VariableAccessUtils.getVariableReferences(variable, method);
|
||||
if (ContainerUtil.exists(references, r -> r.getTextOffset() > argument.getTextOffset())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isKnownExceptionalSideEffectCaught(PsiExpression expression) {
|
||||
String exception = null;
|
||||
if (expression instanceof PsiMethodCallExpression call) {
|
||||
exception = KNOWN_EXCEPTIONAL_SIDE_EFFECTS.mapFirst(call);
|
||||
}
|
||||
else if (expression instanceof PsiMethodReferenceExpression ref) {
|
||||
exception = KNOWN_EXCEPTIONAL_SIDE_EFFECTS.mapFirst(ref);
|
||||
}
|
||||
if (exception == null) return false;
|
||||
PsiClass exceptionClass = JavaPsiFacade.getInstance(expression.getProject()).findClass(exception, expression.getResolveScope());
|
||||
if (exceptionClass == null) return false;
|
||||
PsiTryStatement parentTry = PsiTreeUtil.getParentOfType(expression, PsiTryStatement.class);
|
||||
if (parentTry == null || !PsiTreeUtil.isAncestor(parentTry.getTryBlock(), expression, true)) return false;
|
||||
return ContainerUtil.exists(ExceptionUtils.getExceptionTypesHandled(parentTry),
|
||||
type -> InheritanceUtil.isInheritor(exceptionClass, type.getCanonicalText()));
|
||||
}
|
||||
|
||||
private static boolean isHardcodedException(PsiExpression expression) {
|
||||
if (!(expression instanceof PsiMethodCallExpression call)) return false;
|
||||
if (STREAM_COLLECT.test(call)
|
||||
&& PsiUtil.skipParenthesizedExprDown(call.getArgumentList().getExpressions()[0]) instanceof PsiMethodCallExpression collector
|
||||
&& COLLECTOR_TO_COLLECTION.test(collector)
|
||||
&& PsiUtil.skipParenthesizedExprDown(collector.getArgumentList().getExpressions()[0]) instanceof PsiLambdaExpression lambda) {
|
||||
PsiExpression body = PsiUtil.skipParenthesizedExprDown(LambdaUtil.extractSingleExpressionFromBody(lambda.getBody()));
|
||||
if (body instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiVariable) {
|
||||
// .collect(toCollection(() -> var)) : the result is written into the given collection
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isPureMethod(PsiMethod method, PsiExpression expression) {
|
||||
final boolean honorInferred = Registry.is("ide.ignore.call.result.inspection.honor.inferred.pure");
|
||||
if (!honorInferred && !JavaMethodContractUtil.hasExplicitContractAnnotation(method)) return false;
|
||||
if (!JavaMethodContractUtil.isPure(method) || hasTrivialReturnValue(method)) return false;
|
||||
if (!SideEffectChecker.mayHaveExceptionalSideEffect(method)) return true;
|
||||
if (!(expression instanceof PsiCallExpression call) || JavaMethodContractUtil.getMethodCallContracts(method, null).isEmpty()) return false;
|
||||
CommonDataflow.DataflowResult result = CommonDataflow.getDataflowResult(expression);
|
||||
return result != null && result.cannotFailByContract(call);
|
||||
}
|
||||
|
||||
private static boolean isInTestContainer(PsiExpression call) {
|
||||
PsiElement psiElement = PsiTreeUtil.getNonStrictParentOfType(call, PsiFunctionalExpression.class, PsiAnonymousClass.class);
|
||||
PsiElement expressionList;
|
||||
if (psiElement instanceof PsiFunctionalExpression expression) {
|
||||
PsiType lambdaType = expression.getFunctionalInterfaceType();
|
||||
if (lambdaType == null || InheritanceUtil.isInheritor(lambdaType, JAVA_UTIL_FUNCTION_SUPPLIER)) return false;
|
||||
PsiElement skipParenthesizedExprUp = PsiUtil.skipParenthesizedExprUp(expression);
|
||||
if (skipParenthesizedExprUp == null) return false;
|
||||
expressionList = PsiUtil.skipParenthesizedExprUp(skipParenthesizedExprUp.getParent());
|
||||
}
|
||||
else if (psiElement instanceof PsiAnonymousClass anonymous) {
|
||||
if (!LambdaUtil.isFunctionalType(anonymous.getBaseClassType()) ||
|
||||
InheritanceUtil.isInheritor(anonymous, JAVA_UTIL_FUNCTION_SUPPLIER)) return false;
|
||||
if (!(anonymous.getParent() instanceof PsiNewExpression psiNewExpression)) return false;
|
||||
PsiElement skipParenthesizedExprUp = PsiUtil.skipParenthesizedExprUp(psiNewExpression);
|
||||
if (skipParenthesizedExprUp == null) return false;
|
||||
expressionList = PsiUtil.skipParenthesizedExprUp(skipParenthesizedExprUp.getParent());
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
if (!(expressionList instanceof PsiExpressionList psiExpressionList) ||
|
||||
!(psiExpressionList.getParent() instanceof PsiMethodCallExpression methodCallExpression)) return false;
|
||||
return TEST_OR_MOCK_CONTAINER_METHODS.test(methodCallExpression);
|
||||
}
|
||||
|
||||
private static boolean hasTrivialReturnValue(PsiMethod method) {
|
||||
List<? extends MethodContract> contracts = JavaMethodContractUtil.getMethodCallContracts(method, null);
|
||||
ContractReturnValue nonFailingReturnValue = JavaMethodContractUtil.getNonFailingReturnValue(contracts);
|
||||
return nonFailingReturnValue != null &&
|
||||
(nonFailingReturnValue.equals(ContractReturnValue.returnThis()) ||
|
||||
nonFailingReturnValue instanceof ContractReturnValue.ParameterReturnValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInsight.options.JavaIdentifierValidator;
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.TypeConstraint;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.PsiArrayInitializerExpression;
|
||||
import com.intellij.psi.PsiArrayType;
|
||||
import com.intellij.psi.PsiCallExpression;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.format.FormatDecode;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
public final class MalformedFormatStringInspection extends BaseInspection {
|
||||
public final List<String> classNames;
|
||||
public final List<String> methodNames;
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public @NonNls String additionalClasses = "";
|
||||
/**
|
||||
* @noinspection PublicField
|
||||
*/
|
||||
public @NonNls String additionalMethods = "";
|
||||
|
||||
public MalformedFormatStringInspection() {
|
||||
classNames = new ArrayList<>();
|
||||
methodNames = new ArrayList<>();
|
||||
parseString(additionalClasses, classNames);
|
||||
parseString(additionalMethods, methodNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("classNames", InspectionGadgetsBundle.message("string.format.class.label"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("string.format.choose.class"))),
|
||||
stringList("methodNames", InspectionGadgetsBundle.message("string.format.class.method.label"), new JavaIdentifierValidator())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element node) throws InvalidDataException {
|
||||
super.readSettings(node);
|
||||
parseString(additionalClasses, classNames);
|
||||
parseString(additionalMethods, methodNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
additionalClasses = formatString(classNames);
|
||||
additionalMethods = formatString(methodNames);
|
||||
super.writeSettings(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
final Object value = infos[0];
|
||||
if (value instanceof Exception exception) {
|
||||
final String message = exception.getMessage();
|
||||
if (message != null) {
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.illegal", message);
|
||||
}
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.malformed");
|
||||
}
|
||||
final FormatDecode.Validator[] validators = (FormatDecode.Validator[])value;
|
||||
final int argumentCount = ((Integer)infos[1]).intValue();
|
||||
if (validators.length < argumentCount) {
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.too.many.arguments",
|
||||
argumentCount, validators.length);
|
||||
}
|
||||
if (validators.length > argumentCount) {
|
||||
final boolean isPrefix = ((Boolean)infos[2]).booleanValue();
|
||||
if(isPrefix){
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.at.least.too.few.arguments",
|
||||
argumentCount, validators.length);
|
||||
}else{
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.too.few.arguments",
|
||||
argumentCount, validators.length);
|
||||
}
|
||||
}
|
||||
final PsiType argumentType = (PsiType)infos[2];
|
||||
final FormatDecode.Validator validator = (FormatDecode.Validator)infos[3];
|
||||
String specifier = validator.getInvalidSpecifier(argumentType);
|
||||
return InspectionGadgetsBundle.message("malformed.format.string.problem.descriptor.arguments.do.not.match.type",
|
||||
argumentType.getPresentableText(), specifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new MalformedFormatStringVisitor();
|
||||
}
|
||||
|
||||
private class MalformedFormatStringVisitor extends BaseInspectionVisitor {
|
||||
@Override
|
||||
public void visitCallExpression(@NotNull PsiCallExpression expression) {
|
||||
PsiExpressionList list = expression.getArgumentList();
|
||||
if (list == null) {
|
||||
return;
|
||||
}
|
||||
FormatDecode.FormatArgument formatArgument = FormatDecode.FormatArgument.extract(expression, methodNames, classNames, true);
|
||||
if (formatArgument == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int formatArgumentIndex = formatArgument.getIndex();
|
||||
|
||||
PsiExpression[] arguments = list.getExpressions();
|
||||
|
||||
int argumentCount = arguments.length - formatArgumentIndex;
|
||||
|
||||
String value = formatArgument.calculateValue();
|
||||
final FormatDecode.Validator[] validators;
|
||||
boolean isPrefix = false;
|
||||
if (value != null) {
|
||||
try {
|
||||
validators = FormatDecode.decode(value, argumentCount);
|
||||
}
|
||||
catch (FormatDecode.IllegalFormatException e) {
|
||||
registerError(formatArgument.getExpression(), e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
String prefixValue = formatArgument.calculatePrefixValue();
|
||||
isPrefix = true;
|
||||
if (prefixValue == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
validators = FormatDecode.decodePrefix(prefixValue, argumentCount);
|
||||
}
|
||||
catch (FormatDecode.IllegalFormatException e) {
|
||||
registerError(formatArgument.getExpression(), e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (argumentCount == 1) {
|
||||
final PsiExpression argument = resolveIfPossible(arguments[formatArgumentIndex]);
|
||||
final PsiType argumentType = argument.getType();
|
||||
if (argumentType instanceof PsiArrayType) {
|
||||
final PsiArrayInitializerExpression arrayInitializer;
|
||||
if (argument instanceof PsiNewExpression newExpression) {
|
||||
arrayInitializer = newExpression.getArrayInitializer();
|
||||
}
|
||||
else if (argument instanceof PsiArrayInitializerExpression) {
|
||||
arrayInitializer = (PsiArrayInitializerExpression)argument;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
if (arrayInitializer == null) {
|
||||
return;
|
||||
}
|
||||
arguments = arrayInitializer.getInitializers();
|
||||
argumentCount = arguments.length;
|
||||
formatArgumentIndex = 0;
|
||||
}
|
||||
}
|
||||
if ((!isPrefix && validators.length != argumentCount) ||
|
||||
(isPrefix && validators.length > argumentCount)) {
|
||||
if (expression instanceof PsiMethodCallExpression) {
|
||||
registerMethodCallError((PsiMethodCallExpression)expression, validators, Integer.valueOf(argumentCount), isPrefix);
|
||||
}
|
||||
else if (expression instanceof PsiNewExpression) {
|
||||
registerNewExpressionError((PsiNewExpression)expression, validators, Integer.valueOf(argumentCount), isPrefix);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < validators.length; i++) {
|
||||
final FormatDecode.Validator validator = validators[i];
|
||||
final PsiExpression argument = arguments[i + formatArgumentIndex];
|
||||
final PsiType argumentType = argument.getType();
|
||||
if (argumentType == null) {
|
||||
continue;
|
||||
}
|
||||
if (validator != null && !validator.valid(argumentType)) {
|
||||
PsiType preciseType = TypeConstraint.fromDfType(CommonDataflow.getDfType(argument)).getPsiType(expression.getProject());
|
||||
if (preciseType == null || !validator.valid(preciseType)) {
|
||||
registerError(argument, validators, Integer.valueOf(argumentCount), argumentType, validator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiExpression resolveIfPossible(PsiExpression expression) {
|
||||
expression = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
if (expression instanceof PsiReferenceExpression referenceExpression) {
|
||||
final PsiElement target = referenceExpression.resolve();
|
||||
if (target instanceof PsiVariable variable && target.getContainingFile() == expression.getContainingFile()) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null) {
|
||||
return initializer;
|
||||
}
|
||||
}
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
+723
@@ -0,0 +1,723 @@
|
||||
/*
|
||||
* Copyright 2003-2021 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* 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.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.UnusedSymbolUtil;
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.codeInspection.dataFlow.Mutability;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaRecursiveElementWalkingVisitor;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiArrayType;
|
||||
import com.intellij.psi.PsiAssertStatement;
|
||||
import com.intellij.psi.PsiAssignmentExpression;
|
||||
import com.intellij.psi.PsiCallExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiConditionalExpression;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiEllipsisType;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiExpressionStatement;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiForeachStatement;
|
||||
import com.intellij.psi.PsiInstanceOfExpression;
|
||||
import com.intellij.psi.PsiLambdaExpression;
|
||||
import com.intellij.psi.PsiLocalVariable;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiMethodReferenceExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiParameter;
|
||||
import com.intellij.psi.PsiParenthesizedExpression;
|
||||
import com.intellij.psi.PsiPolyadicExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiSynchronizedStatement;
|
||||
import com.intellij.psi.PsiThisExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeCastExpression;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.CollectionUtils;
|
||||
import com.siyeh.ig.psiutils.ConstructionUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodCallUtils;
|
||||
import com.siyeh.ig.psiutils.SideEffectChecker;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import com.siyeh.ig.ui.ExternalizableStringSet;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.horizontalStack;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
import static com.siyeh.ig.psiutils.ClassUtils.isImmutable;
|
||||
|
||||
public final class MismatchedCollectionQueryUpdateInspection extends BaseInspection {
|
||||
|
||||
private static final CallMatcher TRANSFORMED = CallMatcher.staticCall(
|
||||
CommonClassNames.JAVA_UTIL_COLLECTIONS, "asLifoQueue", "checkedCollection", "checkedList", "checkedMap", "checkedNavigableMap",
|
||||
"checkedNavigableSet", "checkedQueue", "checkedSet", "checkedSortedMap", "checkedSortedSet", "newSetFromMap", "synchronizedCollection",
|
||||
"synchronizedList", "synchronizedMap", "synchronizedNavigableMap", "synchronizedNavigableSet", "synchronizedSet",
|
||||
"synchronizedSortedMap", "synchronizedSortedSet");
|
||||
private static final CallMatcher DERIVED = CallMatcher.anyOf(
|
||||
CollectionUtils.DERIVED_COLLECTION,
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "iterator").parameterCount(0),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "listIterator").parameterCount(0),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "listIterator").parameterTypes("int"),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "subList"),
|
||||
CallMatcher.instanceCall("java.util.SortedMap", "headMap", "tailMap", "subMap"),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_SORTED_SET, "headSet", "tailSet", "subSet"));
|
||||
private static final CallMatcher COLLECTION_SAFE_ARGUMENT_METHODS =
|
||||
CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "addAll", "removeAll", "containsAll", "remove"),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "putAll", "remove")
|
||||
);
|
||||
private static final @NonNls Set<String> COLLECTIONS_QUERIES =
|
||||
Set.of("binarySearch", "disjoint", "indexOfSubList", "lastIndexOfSubList", "max", "min");
|
||||
private static final @NonNls Set<String> COLLECTIONS_UPDATES = Set.of("addAll", "fill", "copy", "replaceAll", "sort");
|
||||
private static final Set<String> COLLECTIONS_ALL =
|
||||
StreamEx.of(COLLECTIONS_QUERIES).append(COLLECTIONS_UPDATES).toImmutableSet();
|
||||
private static final @NotNull CallMatcher QUERY_ITERATOR_METHODS =
|
||||
CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "hasNext", "next", "forEachRemaining"),
|
||||
CallMatcher.instanceCall("java.util.ListIterator", "hasPrevious", "previous", "nextIndex", "previousIndex")
|
||||
);
|
||||
private static final @NotNull CallMatcher NO_UPDATE_FOR_EMPTY =
|
||||
CallMatcher.anyOf(
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_ITERATOR, "remove").parameterCount(0),
|
||||
CallMatcher.instanceCall("java.util.ListIterator", "set").parameterCount(1),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_COLLECTION, "remove", "clear", "removeAll", "retainAll", "removeIf"),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_LIST, "sort", "replaceAll"),
|
||||
CallMatcher.instanceCall(CommonClassNames.JAVA_UTIL_MAP, "replaceAll", "replace", "remove")
|
||||
);
|
||||
private static final Set<String> defaultQueryNames =
|
||||
Set.of("contains", "copyInto", "equals", "forEach", "get", "hashCode", "indexOf", "iterator", "lastIndexOf", "parallelStream", "peek",
|
||||
"propertyNames", "save", "size", "store", "stream", "toArray", "toString", "write");
|
||||
private static final Set<String> defaultUpdateNames =
|
||||
Set.of("add", "clear", "insert", "load", "merge", "offer", "poll", "pop", "push", "put", "remove", "replace", "retain", "sort", "set",
|
||||
"take");
|
||||
@SuppressWarnings("PublicField")
|
||||
public final ExternalizableStringSet queryNames = new ExternalizableStringSet(defaultQueryNames.stream().sorted()
|
||||
.toArray(String[]::new));
|
||||
@SuppressWarnings("PublicField")
|
||||
public final ExternalizableStringSet updateNames = new ExternalizableStringSet(defaultUpdateNames.stream().sorted()
|
||||
.toArray(String[]::new));
|
||||
@SuppressWarnings("PublicField")
|
||||
public final ExternalizableStringSet ignoredClasses = new ExternalizableStringSet();
|
||||
|
||||
private final MismatchedQueryUpdateRunner myRunner = new MismatchedQueryUpdateRunner(queryNames, updateNames, ignoredClasses);
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
horizontalStack(
|
||||
stringList("queryNames", InspectionGadgetsBundle.message("query.label")),
|
||||
stringList("updateNames", InspectionGadgetsBundle.message("update.label"))
|
||||
),
|
||||
stringList("ignoredClasses", InspectionGadgetsBundle.message("ignored.class.label"),
|
||||
new JavaClassValidator().withTitle(InspectionGadgetsBundle.message("ignored.class.names"))
|
||||
.withSuperClass(CommonClassNames.JAVA_UTIL_COLLECTION, CommonClassNames.JAVA_UTIL_MAP))
|
||||
);
|
||||
}
|
||||
|
||||
@Pattern(VALID_ID_PATTERN)
|
||||
@Override
|
||||
public @NotNull String getID() {
|
||||
return "MismatchedQueryAndUpdateOfCollection";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return (String)infos[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabledByDefault() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean runForWholeFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new MismatchedCollectionQueryUpdateVisitor();
|
||||
}
|
||||
|
||||
private record MismatchedQueryUpdateRunner(
|
||||
@NotNull Set<String> queryNames,
|
||||
@NotNull Set<String> updateNames,
|
||||
@NotNull Set<String> ignoredClasses
|
||||
) {
|
||||
@NotNull QueryUpdateInfo compute(@Nullable PsiVariable variable,
|
||||
@Nullable PsiElement context,
|
||||
@NotNull Set<PsiReferenceExpression> ignored) {
|
||||
if (context == null) return QueryUpdateInfo.UNKNOWN;
|
||||
if (variable != null && !checkVariable(variable)) {
|
||||
return QueryUpdateInfo.UNKNOWN;
|
||||
}
|
||||
Visitor visitor = new Visitor(variable, queryNames, updateNames, ignored);
|
||||
context.accept(visitor);
|
||||
return visitor.getInfo();
|
||||
}
|
||||
|
||||
@NotNull QueryUpdateInfo compute(@NotNull PsiVariable variable, @NotNull Set<PsiReferenceExpression> ignored) {
|
||||
final PsiElement context = variable instanceof PsiField ? PsiUtil.getTopLevelClass(variable) :
|
||||
PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
|
||||
return compute(variable, context, ignored);
|
||||
}
|
||||
|
||||
@NotNull QueryUpdateInfo processSingleRef(@NotNull PsiExpression ref) {
|
||||
Visitor visitor = new Visitor(null, queryNames, updateNames, Set.of());
|
||||
visitor.process(ref);
|
||||
return visitor.getInfo();
|
||||
}
|
||||
|
||||
private boolean checkVariable(@NotNull PsiVariable variable) {
|
||||
if (variable instanceof PsiField field) {
|
||||
if (!field.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
PsiClass aClass = field.getContainingClass();
|
||||
if (aClass == null || !aClass.hasModifierProperty(PsiModifier.PRIVATE) || field.hasModifierProperty(PsiModifier.PUBLIC)) {
|
||||
// Public field within private class can be written/read via reflection even without setAccessible hacks,
|
||||
// so we don't analyze such fields to reduce false-positives
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!(variable instanceof PsiLocalVariable)) {
|
||||
return false;
|
||||
}
|
||||
final PsiType type = variable.getType();
|
||||
if (!CollectionUtils.isCollectionClassOrInterface(type)) {
|
||||
return false;
|
||||
}
|
||||
return !ContainerUtil.exists(ignoredClasses, className -> InheritanceUtil.isInheritor(type, className));
|
||||
}
|
||||
}
|
||||
|
||||
private static class Visitor extends JavaRecursiveElementWalkingVisitor {
|
||||
final @Nullable PsiVariable myVariable;
|
||||
final @NotNull Set<PsiVariable> myDerivedVariables = new HashSet<>();
|
||||
final @NotNull Set<String> myQueryNames;
|
||||
final @NotNull Set<String> myUpdateNames;
|
||||
final @NotNull Set<PsiReferenceExpression> myIgnored = new HashSet<>();
|
||||
boolean myQueried;
|
||||
boolean myUpdated;
|
||||
boolean myUpdatedForNonEmpty;
|
||||
boolean myKnownEmpty;
|
||||
|
||||
private Visitor(@Nullable PsiVariable variable,
|
||||
@NotNull Set<String> queryNames,
|
||||
@NotNull Set<String> updateNames,
|
||||
@NotNull Set<PsiReferenceExpression> ignored) {
|
||||
myVariable = variable;
|
||||
myQueryNames = queryNames;
|
||||
myUpdateNames = updateNames;
|
||||
myIgnored.addAll(ignored);
|
||||
if (variable != null) {
|
||||
myKnownEmpty = definitelyEmptyCollection(variable.getInitializer());
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean definitelyEmptyCollection(@Nullable PsiExpression initializer) {
|
||||
return initializer == null || ExpressionUtils.nonStructuralChildren(initializer)
|
||||
.allMatch(e -> ConstructionUtils.isEmptyCollectionInitializer(e));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression ref) {
|
||||
super.visitReferenceExpression(ref);
|
||||
if (myVariable == null) {
|
||||
if (ref.getQualifierExpression() == null) {
|
||||
makeUpdated();
|
||||
makeQueried();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (ref.resolve() instanceof PsiVariable target) {
|
||||
if ((target.equals(myVariable) || myDerivedVariables.contains(target)) && !myIgnored.contains(ref)) {
|
||||
process(ref);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitThisExpression(@NotNull PsiThisExpression expression) {
|
||||
super.visitThisExpression(expression);
|
||||
if (myVariable == null) {
|
||||
process(expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeUpdated() {
|
||||
myUpdated = true;
|
||||
if (myQueried) {
|
||||
stopWalking();
|
||||
}
|
||||
}
|
||||
|
||||
private void makeUpdatedForNonEmpty() {
|
||||
myUpdatedForNonEmpty = true;
|
||||
}
|
||||
|
||||
private void makeQueried() {
|
||||
myQueried = true;
|
||||
if (myUpdated) {
|
||||
stopWalking();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull QueryUpdateInfo getInfo() {
|
||||
return new QueryUpdateInfo(myQueried, myUpdated, myUpdatedForNonEmpty, myKnownEmpty);
|
||||
}
|
||||
|
||||
private void process(PsiExpression reference) {
|
||||
doProcess(findEffectiveReference(reference));
|
||||
}
|
||||
|
||||
private void doProcess(PsiExpression reference) {
|
||||
PsiMethodCallExpression qualifiedCall = ExpressionUtils.getCallForQualifier(reference);
|
||||
if (qualifiedCall != null) {
|
||||
processQualifiedCall(qualifiedCall);
|
||||
return;
|
||||
}
|
||||
if (ExpressionUtils.isVoidContext(reference)) {
|
||||
return;
|
||||
}
|
||||
PsiElement parent = reference.getParent();
|
||||
if (parent instanceof PsiExpressionList args && args.getParent() instanceof PsiCallExpression surroundingCall) {
|
||||
if (surroundingCall instanceof PsiMethodCallExpression methodCall && processCollectionMethods(methodCall, reference)) {
|
||||
return;
|
||||
}
|
||||
makeQueried();
|
||||
if (!isQueryMethod(surroundingCall) && !COLLECTION_SAFE_ARGUMENT_METHODS.matches(surroundingCall)) {
|
||||
makeUpdated();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (myVariable != null && parent instanceof PsiLocalVariable derived && !VariableAccessUtils.variableIsAssigned(derived)) {
|
||||
myDerivedVariables.add(derived);
|
||||
return;
|
||||
}
|
||||
if (parent instanceof PsiMethodReferenceExpression methodReference) {
|
||||
processQualifiedMethodReference(methodReference);
|
||||
return;
|
||||
}
|
||||
if (parent instanceof PsiForeachStatement forEach && forEach.getIteratedValue() == reference) {
|
||||
makeQueried();
|
||||
return;
|
||||
}
|
||||
if (parent instanceof PsiAssignmentExpression assignment && assignment.getLExpression() == reference) {
|
||||
process(assignment);
|
||||
PsiExpression rValue = assignment.getRExpression();
|
||||
if (rValue == null) return;
|
||||
if (ExpressionUtils.nonStructuralChildren(rValue)
|
||||
.allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) {
|
||||
myKnownEmpty = myKnownEmpty && definitelyEmptyCollection(rValue);
|
||||
return;
|
||||
}
|
||||
if (ExpressionUtils.nonStructuralChildren(rValue)
|
||||
.allMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) {
|
||||
makeUpdated();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiPolyadicExpression polyadic) {
|
||||
IElementType tokenType = polyadic.getOperationTokenType();
|
||||
if (tokenType.equals(JavaTokenType.PLUS)) {
|
||||
// String concatenation
|
||||
makeQueried();
|
||||
return;
|
||||
}
|
||||
if (tokenType.equals(JavaTokenType.EQEQ) || tokenType.equals(JavaTokenType.NE)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiAssertStatement assertStatement && assertStatement.getAssertDescription() == reference) {
|
||||
makeQueried();
|
||||
return;
|
||||
}
|
||||
if (parent instanceof PsiInstanceOfExpression || parent instanceof PsiSynchronizedStatement) return;
|
||||
// Any other reference
|
||||
makeUpdated();
|
||||
makeQueried();
|
||||
}
|
||||
|
||||
private void processQualifiedMethodReference(PsiMethodReferenceExpression expression) {
|
||||
final String methodName = expression.getReferenceName();
|
||||
if (isQueryUpdateMethodName(methodName, myQueryNames)) {
|
||||
makeQueried();
|
||||
}
|
||||
if (isQueryUpdateMethodName(methodName, myUpdateNames)) {
|
||||
makeUpdated();
|
||||
}
|
||||
final PsiMethod method = ObjectUtils.tryCast(expression.resolve(), PsiMethod.class);
|
||||
if (method != null &&
|
||||
(!PsiTypes.voidType().equals(method.getReturnType()) &&
|
||||
!PsiTypes.voidType().equals(LambdaUtil.getFunctionalInterfaceReturnType(expression)) ||
|
||||
ContainerUtil.or(method.getParameterList().getParameters(), p -> LambdaUtil.isFunctionalType(p.getType())))) {
|
||||
makeQueried();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean processCollectionMethods(PsiMethodCallExpression call, PsiExpression arg) {
|
||||
PsiExpressionList expressionList = call.getArgumentList();
|
||||
String name = call.getMethodExpression().getReferenceName();
|
||||
if (!COLLECTIONS_ALL.contains(name) || !isCollectionsClassMethod(call)) return false;
|
||||
if (COLLECTIONS_QUERIES.contains(name) && !(call.getParent() instanceof PsiExpressionStatement)) {
|
||||
makeQueried();
|
||||
return true;
|
||||
}
|
||||
if (COLLECTIONS_UPDATES.contains(name)) {
|
||||
int index = ArrayUtil.indexOf(expressionList.getExpressions(), arg);
|
||||
if (index == 0) {
|
||||
makeUpdated();
|
||||
}
|
||||
else {
|
||||
makeQueried();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void processQualifiedCall(PsiMethodCallExpression call) {
|
||||
boolean voidContext = ExpressionUtils.isVoidContext(call);
|
||||
String name = call.getMethodExpression().getReferenceName();
|
||||
PsiExpression qualifier = call.getMethodExpression().getQualifierExpression();
|
||||
if (qualifier != null && InheritanceUtil.isInheritor(qualifier.getType(), CommonClassNames.JAVA_UTIL_ITERATOR)) {
|
||||
makeQueried();
|
||||
if (!QUERY_ITERATOR_METHODS.test(call)) {
|
||||
makeUpdatedCall(call);
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean queryQualifier = isQueryUpdateMethodName(name, myQueryNames);
|
||||
boolean updateQualifier = isQueryUpdateMethodName(name, myUpdateNames);
|
||||
if (queryQualifier &&
|
||||
(!voidContext || PsiTypes.voidType().equals(call.getType()) || "toArray".equals(name) && !call.getArgumentList().isEmpty())) {
|
||||
makeQueried();
|
||||
}
|
||||
if (updateQualifier) {
|
||||
makeUpdatedCall(call);
|
||||
if (!voidContext) {
|
||||
makeQueried();
|
||||
}
|
||||
else {
|
||||
for (PsiExpression arg : call.getArgumentList().getExpressions()) {
|
||||
PsiParameter parameter = MethodCallUtils.getParameterForArgument(arg);
|
||||
if (parameter != null && LambdaUtil.isFunctionalType(parameter.getType())) {
|
||||
if (ExpressionUtils.nonStructuralChildren(arg).anyMatch(e -> mayHaveSideEffect(e))) {
|
||||
makeQueried();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.getArgumentList().getExpressionCount() == 2 &&
|
||||
("poll".equals(name) || "pollFirst".equals(name) || "pollLast".equals(name)) &&
|
||||
TypeUtils.variableHasTypeOrSubtype(myVariable, "java.util.concurrent.BlockingQueue")) {
|
||||
// poll(timeout, unit) on a blocking queue/dequeue may be considered querying, even if the result is not used,
|
||||
// because the thread will be blocked until a value is received (or a timeout happens).
|
||||
makeQueried();
|
||||
}
|
||||
else if (("take".equals(name) || "takeFirst".equals(name) || "takeLast".equals(name)) &&
|
||||
TypeUtils.variableHasTypeOrSubtype(myVariable, "java.util.concurrent.BlockingQueue")) {
|
||||
// take() on a blocking queue/dequeue may be considered querying, even if the result is not used.
|
||||
// because the thread will be blocked until a value is received.
|
||||
makeQueried();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!queryQualifier && !updateQualifier) {
|
||||
if (!isQueryMethod(call)) {
|
||||
makeUpdated();
|
||||
}
|
||||
makeQueried();
|
||||
}
|
||||
}
|
||||
|
||||
private void makeUpdatedCall(PsiMethodCallExpression call) {
|
||||
if (NO_UPDATE_FOR_EMPTY.test(call)) {
|
||||
makeUpdatedForNonEmpty();
|
||||
}
|
||||
else {
|
||||
makeUpdated();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean mayHaveSideEffect(PsiExpression fn) {
|
||||
if (fn instanceof PsiLambdaExpression) {
|
||||
PsiElement body = ((PsiLambdaExpression)fn).getBody();
|
||||
if (body != null) {
|
||||
return SideEffectChecker.mayHaveSideEffects(body, x -> false);
|
||||
}
|
||||
}
|
||||
if (fn instanceof PsiMethodReferenceExpression) {
|
||||
PsiElement target = ((PsiMethodReferenceExpression)fn).resolve();
|
||||
return !(target instanceof PsiMethod) || !JavaMethodContractUtil.isPure((PsiMethod)target);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static PsiExpression findEffectiveReference(PsiExpression expression) {
|
||||
while (true) {
|
||||
PsiElement parent = expression.getParent();
|
||||
if (parent instanceof PsiParenthesizedExpression || parent instanceof PsiTypeCastExpression ||
|
||||
parent instanceof PsiConditionalExpression) {
|
||||
expression = (PsiExpression)parent;
|
||||
continue;
|
||||
}
|
||||
if (parent instanceof PsiReferenceExpression) {
|
||||
PsiMethodCallExpression grandParent = ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (DERIVED.test(grandParent)) {
|
||||
expression = grandParent;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiExpressionList) {
|
||||
PsiMethodCallExpression grandParent = ObjectUtils.tryCast(parent.getParent(), PsiMethodCallExpression.class);
|
||||
if (TRANSFORMED.test(grandParent)) {
|
||||
expression = grandParent;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
|
||||
private static boolean isEmptyCollectionInitializer(PsiExpression initializer) {
|
||||
if (!(initializer instanceof PsiNewExpression newExpression)) {
|
||||
return ConstructionUtils.isEmptyCollectionInitializer(initializer);
|
||||
}
|
||||
final PsiExpressionList argumentList = newExpression.getArgumentList();
|
||||
if (argumentList == null) {
|
||||
return false;
|
||||
}
|
||||
PsiMethod ctor = newExpression.resolveMethod();
|
||||
if (ctor == null) return true;
|
||||
PsiParameter[] parameters = ctor.getParameterList().getParameters();
|
||||
final PsiExpression[] arguments = argumentList.getExpressions();
|
||||
if (ctor.isVarArgs() && arguments.length >= parameters.length) {
|
||||
return false;
|
||||
}
|
||||
for (PsiParameter parameter : parameters) {
|
||||
PsiType type = parameter.getType();
|
||||
if (CollectionUtils.isCollectionClassOrInterface(type)) {
|
||||
return false;
|
||||
}
|
||||
if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_ITERATOR)) {
|
||||
return false;
|
||||
}
|
||||
if (type instanceof PsiArrayType && !(type instanceof PsiEllipsisType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isCollectionInitializer(PsiExpression initializer) {
|
||||
return isEmptyCollectionInitializer(initializer) || ConstructionUtils.isPrepopulatedCollectionInitializer(initializer);
|
||||
}
|
||||
|
||||
private static boolean isQueryUpdateMethodName(String methodName, Set<String> myNames) {
|
||||
if (methodName == null) {
|
||||
return false;
|
||||
}
|
||||
if (myNames.contains(methodName)) {
|
||||
return true;
|
||||
}
|
||||
for (String updateName : myNames) {
|
||||
if (methodName.startsWith(updateName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isCollectionsClassMethod(PsiMethodCallExpression call) {
|
||||
final PsiMethod method = call.resolveMethod();
|
||||
if (method == null) return false;
|
||||
final PsiClass aClass = method.getContainingClass();
|
||||
if (aClass == null) return false;
|
||||
final String qualifiedName = aClass.getQualifiedName();
|
||||
return CommonClassNames.JAVA_UTIL_COLLECTIONS.equals(qualifiedName);
|
||||
}
|
||||
|
||||
private static boolean isQueryMethod(@NotNull PsiCallExpression call) {
|
||||
PsiType type = call.getType();
|
||||
boolean immutable = isImmutable(type);
|
||||
// If pure method returns mutable object, then it's possible that further mutation of that object will modify the original collection
|
||||
if (!immutable) {
|
||||
immutable = call instanceof PsiNewExpression && CollectionUtils.isConcreteCollectionClass(type);
|
||||
}
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (!immutable && method != null) {
|
||||
PsiClass returnType = PsiUtil.resolveClassInClassTypeOnly(method.getReturnType());
|
||||
if (returnType instanceof PsiTypeParameter) {
|
||||
// method returning unbounded type parameter is unlikely to allow modify original collection via the returned value
|
||||
immutable = ((PsiTypeParameter)returnType).getExtendsList().getReferencedTypes().length == 0;
|
||||
}
|
||||
if (!immutable) {
|
||||
immutable = Mutability.getMutability(method).isUnmodifiable();
|
||||
}
|
||||
}
|
||||
return immutable && !SideEffectChecker.mayHaveSideEffects(call);
|
||||
}
|
||||
|
||||
private record QueryUpdateInfo(boolean queried, boolean updated, boolean updatedForNonEmpty, boolean knownEmpty) {
|
||||
static final QueryUpdateInfo UNKNOWN = new QueryUpdateInfo(true, true, true, false);
|
||||
}
|
||||
|
||||
private class MismatchedCollectionQueryUpdateVisitor extends BaseInspectionVisitor {
|
||||
private void check(@NotNull PsiVariable variable) {
|
||||
QueryUpdateInfo info = myRunner.compute(variable, Set.of());
|
||||
final boolean written = info.updated || updatedViaInitializer(variable);
|
||||
final boolean read = info.queried || queriedViaInitializer(variable);
|
||||
if (!written && info.updatedForNonEmpty && info.knownEmpty) {
|
||||
registerVariableError(variable,
|
||||
InspectionGadgetsBundle.message("mismatched.update.collection.problem.description.no.effect.updates"));
|
||||
return;
|
||||
}
|
||||
if (read == written) {
|
||||
return;
|
||||
}
|
||||
String message = written ? InspectionGadgetsBundle.message("mismatched.update.collection.problem.description.updated.not.queried") :
|
||||
info.knownEmpty ? InspectionGadgetsBundle.message(
|
||||
"mismatched.update.collection.problem.description.queried.empty") :
|
||||
InspectionGadgetsBundle.message("mismatched.update.collection.problem.description.queried.not.updated");
|
||||
if (written) {
|
||||
PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null) {
|
||||
List<PsiExpression> expressions = ExpressionUtils.nonStructuralChildren(initializer).toList();
|
||||
if (!ContainerUtil.and(expressions, MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)) {
|
||||
expressions.stream().filter(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer)
|
||||
.forEach(emptyCollection -> registerError(emptyCollection, message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
registerVariableError(variable, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (UnusedSymbolUtil.isImplicitWrite(field) || UnusedSymbolUtil.isImplicitRead(field)) {
|
||||
// Even implicit read of the mutable collection field may cause collection change
|
||||
return;
|
||||
}
|
||||
check(field);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
super.visitLocalVariable(variable);
|
||||
check(variable);
|
||||
}
|
||||
|
||||
private boolean updatedViaInitializer(PsiVariable variable) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null &&
|
||||
!ExpressionUtils.nonStructuralChildren(initializer)
|
||||
.allMatch(MismatchedCollectionQueryUpdateInspection::isEmptyCollectionInitializer)) {
|
||||
return true;
|
||||
}
|
||||
if (initializer instanceof PsiNewExpression newExpression) {
|
||||
final PsiAnonymousClass anonymousClass = newExpression.getAnonymousClass();
|
||||
if (anonymousClass != null) {
|
||||
if (myRunner.compute(null, anonymousClass, Set.of()).updated) {
|
||||
return true;
|
||||
}
|
||||
final ThisPassedAsArgumentVisitor visitor = new ThisPassedAsArgumentVisitor();
|
||||
anonymousClass.accept(visitor);
|
||||
if (visitor.isPassed()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean queriedViaInitializer(PsiVariable variable) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
return initializer != null &&
|
||||
ExpressionUtils.nonStructuralChildren(initializer)
|
||||
.noneMatch(MismatchedCollectionQueryUpdateInspection::isCollectionInitializer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param collectCall a method call returning a list (e.g. {@code Stream.of(1, 2, 3).collect(Collectors.toList())})
|
||||
* @return {@code true} if it's known that the result of {@code collectCall} is never updated;
|
||||
* {@code false} if updated or not known
|
||||
*/
|
||||
@Contract("null -> false")
|
||||
public static boolean isUnmodified(@Nullable PsiMethodCallExpression collectCall) {
|
||||
if (collectCall == null) return false;
|
||||
final PsiExpression effectiveReference = findEffectiveReference(collectCall);
|
||||
MismatchedQueryUpdateRunner runner = new MismatchedQueryUpdateRunner(defaultQueryNames, defaultUpdateNames, Set.of());
|
||||
QueryUpdateInfo info = runner.processSingleRef(collectCall);
|
||||
if (!info.updated && !info.updatedForNonEmpty) return true;
|
||||
PsiElement parent = effectiveReference.getParent();
|
||||
Set<PsiReferenceExpression> ignored = new HashSet<>();
|
||||
if (parent instanceof PsiAssignmentExpression assignmentExpression) {
|
||||
// Do not process when the result of assignment is used: rare case
|
||||
if (!ExpressionUtils.isVoidContext(assignmentExpression)) return false;
|
||||
if(assignmentExpression.getLExpression() instanceof PsiReferenceExpression referenceExpression){
|
||||
ignored.add(referenceExpression);
|
||||
parent = referenceExpression.resolve();
|
||||
}
|
||||
}
|
||||
if (parent instanceof PsiVariable variable) {
|
||||
info = runner.compute(variable, variable instanceof PsiField ? Set.of() : ignored);
|
||||
return !info.updated && !info.updatedForNonEmpty;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2003-2007 Dave Griffith, Bas Leijdekkers
|
||||
*
|
||||
* 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.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiParameterList;
|
||||
import com.siyeh.HardcodedMethodConstants;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.RenameFix;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class MisspelledEqualsInspection extends BaseInspection {
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix buildFix(Object... infos) {
|
||||
return new RenameFix(HardcodedMethodConstants.EQUALS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"misspelled.equals.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new MisspelledEqualsVisitor();
|
||||
}
|
||||
|
||||
private static class MisspelledEqualsVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
//note: no call to super
|
||||
final @NonNls String methodName = method.getName();
|
||||
if (!"equal".equals(methodName)) {
|
||||
return;
|
||||
}
|
||||
final PsiParameterList parameterList = method.getParameterList();
|
||||
if (parameterList.getParametersCount() != 1) {
|
||||
return;
|
||||
}
|
||||
registerMethodError(method);
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInsight.options.JavaClassValidator;
|
||||
import com.intellij.codeInspection.AddToInspectionOptionListFix;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiArrayType;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiJavaCodeReferenceElement;
|
||||
import com.intellij.psi.PsiMethodReferenceExpression;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiTypeElement;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.OrderedSet;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.fixes.SuppressForTestsScopeFix;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
import static com.intellij.codeInspection.options.OptPane.stringList;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class ResultOfObjectAllocationIgnoredInspection extends BaseInspection {
|
||||
|
||||
@SuppressWarnings("PublicField") public OrderedSet<String> ignoredClasses = new OrderedSet<>();
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
stringList("ignoredClasses", InspectionGadgetsBundle.message("options.label.ignored.classes"),
|
||||
new JavaClassValidator().withTitle(
|
||||
InspectionGadgetsBundle.message("result.of.object.allocation.ignored.options.chooserTitle"))));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiElement context = (PsiElement)infos[0];
|
||||
return SuppressForTestsScopeFix.build(this, context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalQuickFix @NotNull [] buildFixes(Object... infos) {
|
||||
final List<LocalQuickFix> result = new SmartList<>();
|
||||
final PsiExpression expression = (PsiExpression)infos[0];
|
||||
final PsiClass aClass = PsiUtil.resolveClassInClassTypeOnly(expression.getType());
|
||||
if (aClass != null) {
|
||||
final String name = aClass.getQualifiedName();
|
||||
if (name != null) {
|
||||
result.add(new AddToInspectionOptionListFix<>(this, InspectionGadgetsBundle.message("result.of.object.allocation.fix.name", name),
|
||||
name, tool -> tool.ignoredClasses));
|
||||
}
|
||||
}
|
||||
ContainerUtil.addIfNotNull(result, SuppressForTestsScopeFix.build(this, expression));
|
||||
return result.toArray(LocalQuickFix.EMPTY_ARRAY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
if (infos[0] instanceof PsiMethodReferenceExpression) {
|
||||
return InspectionGadgetsBundle.message("result.of.object.allocation.ignored.problem.descriptor.methodRef");
|
||||
}
|
||||
return InspectionGadgetsBundle.message("result.of.object.allocation.ignored.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ResultOfObjectAllocationIgnoredVisitor();
|
||||
}
|
||||
|
||||
private class ResultOfObjectAllocationIgnoredVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) {
|
||||
super.visitMethodReferenceExpression(expression);
|
||||
if (PsiTypes.voidType().equals(LambdaUtil.getFunctionalInterfaceReturnType(expression))) {
|
||||
if (expression.isConstructor()) {
|
||||
PsiElement qualifier = expression.getQualifier();
|
||||
if (qualifier instanceof PsiReferenceExpression ref && ref.resolve() instanceof PsiClass cls &&
|
||||
!ignoredClasses.contains(cls.getQualifiedName())) {
|
||||
registerError(expression, expression);
|
||||
}
|
||||
if (qualifier instanceof PsiTypeElement typeElement && typeElement.getType() instanceof PsiArrayType) {
|
||||
registerError(expression, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(@NotNull PsiNewExpression expression) {
|
||||
super.visitNewExpression(expression);
|
||||
if (!ExpressionUtils.isVoidContext(expression)) {
|
||||
return;
|
||||
}
|
||||
if (expression.isArrayCreation()) {
|
||||
return;
|
||||
}
|
||||
final PsiJavaCodeReferenceElement reference = expression.getClassOrAnonymousClassReference();
|
||||
if (reference == null) {
|
||||
return;
|
||||
}
|
||||
final PsiElement target = reference.resolve();
|
||||
if (!(target instanceof PsiClass aClass)) {
|
||||
return;
|
||||
}
|
||||
if (!(expression instanceof PsiAnonymousClass) && ignoredClasses.contains(aClass.getQualifiedName())) {
|
||||
return;
|
||||
}
|
||||
registerNewExpressionError(expression, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInsight.Nullability;
|
||||
import com.intellij.codeInsight.NullabilityAnnotationInfo;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationModCommandAction;
|
||||
import com.intellij.codeInsight.options.JavaInspectionButtons;
|
||||
import com.intellij.codeInsight.options.JavaInspectionControls;
|
||||
import com.intellij.codeInspection.CommonQuickFixBundle;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.dataFlow.DfaPsiUtil;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.codeInspection.dataFlow.StandardMethodContract;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.util.OptionalUtil;
|
||||
import com.intellij.java.syntax.parser.JavaKeywords;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.PsiUpdateModCommandQuickFix;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiAnonymousClass;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiClassType;
|
||||
import com.intellij.psi.PsiCodeBlock;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiExpressionList;
|
||||
import com.intellij.psi.PsiLambdaExpression;
|
||||
import com.intellij.psi.PsiLiteralExpression;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiReturnStatement;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.controlFlow.DefUseUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.PsiReplacementUtil;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.CollectionUtils;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.intellij.lang.annotations.Pattern;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class ReturnNullInspection extends BaseInspection {
|
||||
|
||||
private static final CallMatcher.Simple MAP_COMPUTE =
|
||||
CallMatcher.instanceCall("java.util.Map", "compute", "computeIfPresent", "computeIfAbsent");
|
||||
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean m_reportObjectMethods = true;
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean m_reportArrayMethods = true;
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean m_reportCollectionMethods = true;
|
||||
@SuppressWarnings("PublicField")
|
||||
public boolean m_ignorePrivateMethods = false;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("m_ignorePrivateMethods", InspectionGadgetsBundle.message("return.of.null.ignore.private.option")),
|
||||
checkbox("m_reportArrayMethods", InspectionGadgetsBundle.message("return.of.null.arrays.option")),
|
||||
checkbox("m_reportCollectionMethods", InspectionGadgetsBundle.message("return.of.null.collections.option")),
|
||||
checkbox("m_reportObjectMethods", InspectionGadgetsBundle.message("return.of.null.objects.option")),
|
||||
JavaInspectionControls.button(JavaInspectionButtons.ButtonKind.NULLABILITY_ANNOTATIONS));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Pattern("[a-zA-Z_0-9.-]+")
|
||||
public @NotNull String getID() {
|
||||
return "ReturnOfNull";
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message(
|
||||
"return.of.null.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable LocalQuickFix buildFix(Object... infos) {
|
||||
final PsiElement elt = (PsiElement)infos[0];
|
||||
final PsiMethod method = PsiTreeUtil.getParentOfType(elt, PsiMethod.class, false, PsiLambdaExpression.class);
|
||||
if (method == null) return null;
|
||||
final PsiType type = method.getReturnType();
|
||||
if (TypeUtils.isOptional(type)) {
|
||||
// don't suggest to annotate Optional methods as Nullable
|
||||
return new ReplaceWithEmptyOptionalFix(((PsiClassType)type).rawType().getCanonicalText());
|
||||
}
|
||||
|
||||
return LocalQuickFix.from(AddAnnotationModCommandAction.createAddNullableFix(method));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new ReturnNullVisitor();
|
||||
}
|
||||
|
||||
private static class ReplaceWithEmptyOptionalFix extends PsiUpdateModCommandQuickFix {
|
||||
|
||||
private final String myTypeText;
|
||||
|
||||
ReplaceWithEmptyOptionalFix(String typeText) {
|
||||
myTypeText = typeText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nls @NotNull String getName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.with.x", getReplacementText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nls @NotNull String getFamilyName() {
|
||||
return CommonQuickFixBundle.message("fix.replace.with.x", "Optional.empty()");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
if (!(element instanceof PsiLiteralExpression literalExpression)) {
|
||||
return;
|
||||
}
|
||||
PsiReplacementUtil.replaceExpression(literalExpression, getReplacementText());
|
||||
}
|
||||
|
||||
private @NonNls @NotNull String getReplacementText() {
|
||||
return myTypeText + "." + (OptionalUtil.GUAVA_OPTIONAL.equals(myTypeText) ? "absent" : "empty") + "()";
|
||||
}
|
||||
}
|
||||
|
||||
private class ReturnNullVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(@NotNull PsiLiteralExpression value) {
|
||||
super.visitLiteralExpression(value);
|
||||
final String text = value.getText();
|
||||
if (!JavaKeywords.NULL.equals(text)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement parent = ExpressionUtils.getPassThroughParent(value);
|
||||
if (!(parent instanceof PsiReturnStatement) && !(parent instanceof PsiLambdaExpression)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement element = PsiTreeUtil.getParentOfType(value, PsiMethod.class, PsiLambdaExpression.class);
|
||||
final PsiMethod method;
|
||||
final PsiType returnType;
|
||||
final boolean lambda;
|
||||
if (element instanceof PsiMethod) {
|
||||
method = (PsiMethod)element;
|
||||
returnType = method.getReturnType();
|
||||
lambda = false;
|
||||
}
|
||||
else if (element instanceof PsiLambdaExpression) {
|
||||
final PsiType functionalInterfaceType = ((PsiLambdaExpression)element).getFunctionalInterfaceType();
|
||||
method = LambdaUtil.getFunctionalInterfaceMethod(functionalInterfaceType);
|
||||
returnType = LambdaUtil.getFunctionalInterfaceReturnType(functionalInterfaceType);
|
||||
lambda = true;
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
if (method == null || returnType == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (TypeUtils.isOptional(returnType)) {
|
||||
registerError(value, value);
|
||||
return;
|
||||
}
|
||||
if (lambda) {
|
||||
if (m_ignorePrivateMethods || isInNullableContext(element)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (m_ignorePrivateMethods && method.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
return;
|
||||
}
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass instanceof PsiAnonymousClass) {
|
||||
if (m_ignorePrivateMethods || isInNullableContext(containingClass.getParent())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
final Project project = method.getProject();
|
||||
final NullabilityAnnotationInfo info = NullableNotNullManager.getInstance(project).findEffectiveNullabilityInfo(method);
|
||||
if (info != null && info.getNullability() == Nullability.NULLABLE && !info.isInferred()) {
|
||||
return;
|
||||
}
|
||||
if (DfaPsiUtil.getTypeNullability(returnType) == Nullability.NULLABLE) {
|
||||
return;
|
||||
}
|
||||
if (!lambda && JavaMethodContractUtil.hasExplicitContractAnnotation(method)) {
|
||||
List<StandardMethodContract> contracts = JavaMethodContractUtil.getMethodContracts(method);
|
||||
if (ContainerUtil.exists(contracts, c -> c.getReturnValue().isNull())) return;
|
||||
}
|
||||
|
||||
if (CollectionUtils.isCollectionClassOrInterface(returnType)) {
|
||||
if (m_reportCollectionMethods) {
|
||||
registerError(value, value);
|
||||
}
|
||||
}
|
||||
else if (returnType.getArrayDimensions() > 0) {
|
||||
if (m_reportArrayMethods) {
|
||||
registerError(value, value);
|
||||
}
|
||||
}
|
||||
else if (!returnType.equalsToText("java.lang.Void")){
|
||||
if (m_reportObjectMethods) {
|
||||
registerError(value, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isInNullableContext(PsiElement element) {
|
||||
final PsiElement parent = element instanceof PsiExpression ? ExpressionUtils.getPassThroughParent((PsiExpression)element) : element;
|
||||
if (parent instanceof PsiVariable variable) {
|
||||
final PsiCodeBlock codeBlock = PsiTreeUtil.getParentOfType(variable, PsiCodeBlock.class);
|
||||
if (codeBlock == null) {
|
||||
return false;
|
||||
}
|
||||
final PsiElement[] refs = DefUseUtil.getRefs(codeBlock, variable, element);
|
||||
return ContainerUtil.exists(refs, this::isInNullableContext);
|
||||
}
|
||||
else if (parent instanceof PsiExpressionList) {
|
||||
final PsiElement grandParent = parent.getParent();
|
||||
if (grandParent instanceof PsiMethodCallExpression methodCallExpression) {
|
||||
return MAP_COMPUTE.test(methodCallExpression);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.UpdateInspectionOptionFix;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.PsiClassType;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiJavaCodeReferenceElement;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeParameter;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class SortedCollectionWithNonComparableKeysInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private static final Set<String> COLLECTIONS = Set.of(
|
||||
"java.util.TreeSet", "java.util.TreeMap", "java.util.concurrent.ConcurrentSkipListSet", "java.util.concurrent.ConcurrentSkipListMap");
|
||||
|
||||
public boolean IGNORE_TYPE_PARAMETERS;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("IGNORE_TYPE_PARAMETERS",
|
||||
JavaBundle.message("inspection.sorted.collection.with.non.comparable.keys.option.type.parameters")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitNewExpression(@NotNull PsiNewExpression expression) {
|
||||
if (expression.getAnonymousClass() != null || expression.isArrayCreation() ||
|
||||
expression.getArgumentList() == null || !expression.getArgumentList().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
PsiJavaCodeReferenceElement reference = expression.getClassReference();
|
||||
if (reference == null) return;
|
||||
String qualifiedName = reference.getQualifiedName();
|
||||
if (!COLLECTIONS.contains(qualifiedName)) return;
|
||||
PsiClassType type = ObjectUtils.tryCast(expression.getType(), PsiClassType.class);
|
||||
if (type == null || type.isRaw()) return;
|
||||
PsiType elementType = ArrayUtil.getFirstElement(type.getParameters());
|
||||
if (elementType == null || TypeUtils.isJavaLangObject(elementType)) return;
|
||||
ModCommandAction fix = null;
|
||||
if (elementType instanceof PsiClassType && ((PsiClassType)elementType).resolve() instanceof PsiTypeParameter) {
|
||||
if (IGNORE_TYPE_PARAMETERS) return;
|
||||
String message = JavaBundle.message("inspection.sorted.collection.with.non.comparable.keys.option.type.parameters");
|
||||
fix = new UpdateInspectionOptionFix(SortedCollectionWithNonComparableKeysInspection.this, "IGNORE_TYPE_PARAMETERS", message, true);
|
||||
}
|
||||
if (InheritanceUtil.isInheritor(elementType, CommonClassNames.JAVA_LANG_COMPARABLE)) return;
|
||||
holder.problem(expression, JavaBundle.message("inspection.sorted.collection.with.non.comparable.keys.message"))
|
||||
.maybeFix(fix).register();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.CommonDataflow;
|
||||
import com.intellij.codeInspection.dataFlow.jvm.JvmPsiRangeSetUtil;
|
||||
import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.codeInspection.options.OptionController;
|
||||
import com.intellij.openapi.util.InvalidDataException;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaTokenType;
|
||||
import com.intellij.psi.LambdaUtil;
|
||||
import com.intellij.psi.PsiBinaryExpression;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiLambdaExpression;
|
||||
import com.intellij.psi.PsiMember;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiTypeCastExpression;
|
||||
import com.intellij.psi.PsiTypes;
|
||||
import com.intellij.psi.tree.IElementType;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.BaseInspection;
|
||||
import com.siyeh.ig.BaseInspectionVisitor;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.MethodMatcher;
|
||||
import com.siyeh.ig.psiutils.MethodUtils;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public final class SubtractionInCompareToInspection extends BaseInspection {
|
||||
|
||||
private final MethodMatcher methodMatcher;
|
||||
|
||||
public SubtractionInCompareToInspection() {
|
||||
methodMatcher = new MethodMatcher()
|
||||
.add(CommonClassNames.JAVA_UTIL_COLLECTION, "size")
|
||||
.add(CommonClassNames.JAVA_UTIL_MAP, "size")
|
||||
.add(CommonClassNames.JAVA_LANG_STRING, "length")
|
||||
.add(CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER, "length")
|
||||
.finishDefault();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return OptPane.pane(methodMatcher.getTable(""));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull OptionController getOptionController() {
|
||||
return methodMatcher.getOptionController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readSettings(@NotNull Element node) throws InvalidDataException {
|
||||
super.readSettings(node);
|
||||
methodMatcher.readSettings(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSettings(@NotNull Element node) throws WriteExternalException {
|
||||
super.writeSettings(node);
|
||||
methodMatcher.writeSettings(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String buildErrorString(Object... infos) {
|
||||
return InspectionGadgetsBundle.message("subtraction.in.compareto.problem.descriptor");
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull BaseInspectionVisitor buildVisitor() {
|
||||
return new SubtractionInCompareToVisitor();
|
||||
}
|
||||
|
||||
private class SubtractionInCompareToVisitor extends BaseInspectionVisitor {
|
||||
|
||||
@Override
|
||||
public void visitBinaryExpression(@NotNull PsiBinaryExpression expression) {
|
||||
super.visitBinaryExpression(expression);
|
||||
final IElementType tokenType = expression.getOperationTokenType();
|
||||
if (!tokenType.equals(JavaTokenType.MINUS) || isSafeSubtraction(expression)) {
|
||||
return;
|
||||
}
|
||||
final PsiLambdaExpression lambdaExpression =
|
||||
PsiTreeUtil.getParentOfType(expression, PsiLambdaExpression.class, true, PsiMember.class);
|
||||
if (lambdaExpression != null) {
|
||||
final PsiClass functionalInterface = LambdaUtil.resolveFunctionalInterfaceClass(lambdaExpression);
|
||||
if (functionalInterface != null && CommonClassNames.JAVA_UTIL_COMPARATOR.equals(functionalInterface.getQualifiedName())) {
|
||||
registerError(expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
final PsiMethod method = PsiTreeUtil.getParentOfType(expression, PsiMethod.class, true, PsiClass.class, PsiLambdaExpression.class);
|
||||
if (!MethodUtils.isCompareTo(method) && !MethodUtils.isComparatorCompare(method)) {
|
||||
return;
|
||||
}
|
||||
registerError(expression);
|
||||
}
|
||||
|
||||
private boolean isSafeSubtraction(PsiBinaryExpression binaryExpression) {
|
||||
final PsiType type = binaryExpression.getType();
|
||||
if (type == null) return true;
|
||||
if (PsiTypes.floatType().equals(type) || PsiTypes.doubleType().equals(type)) {
|
||||
// Difference of floats and doubles never overflows.
|
||||
// It may lose a precision, but it's not the case when we compare the result with zero
|
||||
PsiElement parent = PsiUtil.skipParenthesizedExprUp(binaryExpression.getParent());
|
||||
if(parent instanceof PsiTypeCastExpression) {
|
||||
PsiType castType = ((PsiTypeCastExpression)parent).getType();
|
||||
if(PsiTypes.intType().equals(castType) || PsiTypes.longType().equals(castType)) {
|
||||
// Precision is lost if result is cast to int/long (e.g. (int)(1.0 - 0.5) == 0)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (ExpressionUtils.isEvaluatedAtCompileTime(binaryExpression)) {
|
||||
// If compile time expression overflows, we have separate NumericOverflowInspection for this
|
||||
return true;
|
||||
}
|
||||
final PsiExpression lhs = binaryExpression.getLOperand();
|
||||
final PsiExpression rhs = binaryExpression.getROperand();
|
||||
if (rhs == null) return true;
|
||||
final PsiType lhsType = lhs.getType();
|
||||
final PsiType rhsType = rhs.getType();
|
||||
if (lhsType == null || rhsType == null) {
|
||||
return false;
|
||||
}
|
||||
if ((PsiTypes.byteType().equals(lhsType) || PsiTypes.shortType().equals(lhsType) || PsiTypes.charType().equals(lhsType)) &&
|
||||
(PsiTypes.byteType().equals(rhsType) || PsiTypes.shortType().equals(rhsType) || PsiTypes.charType().equals(rhsType))) {
|
||||
return true;
|
||||
}
|
||||
if (isSafeOperand(lhs) && isSafeOperand(rhs)) return true;
|
||||
LongRangeSet leftRange = CommonDataflow.getExpressionRange(lhs);
|
||||
LongRangeSet rightRange = CommonDataflow.getExpressionRange(rhs);
|
||||
if (leftRange != null && !leftRange.isEmpty() && rightRange != null && !rightRange.isEmpty()) {
|
||||
if (!leftRange.subtractionMayOverflow(rightRange, JvmPsiRangeSetUtil.getLongRangeType(type))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSafeOperand(PsiExpression operand) {
|
||||
operand = PsiUtil.skipParenthesizedExprDown(operand);
|
||||
if (operand instanceof PsiMethodCallExpression methodCallExpression) {
|
||||
return methodMatcher.matches(methodCallExpression);
|
||||
}
|
||||
return ExpressionUtils.getArrayFromLengthExpression(operand) != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.siyeh.ig.bugs;
|
||||
|
||||
import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.dataFlow.ContractReturnValue;
|
||||
import com.intellij.codeInspection.dataFlow.JavaMethodContractUtil;
|
||||
import com.intellij.codeInspection.dataFlow.Mutability;
|
||||
import com.intellij.codeInspection.dataFlow.MutationSignature;
|
||||
import com.intellij.codeInspection.options.OptPane;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.JavaElementVisitor;
|
||||
import com.intellij.psi.PsiCallExpression;
|
||||
import com.intellij.psi.PsiClassType;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiField;
|
||||
import com.intellij.psi.PsiIdentifier;
|
||||
import com.intellij.psi.PsiJavaCodeReferenceElement;
|
||||
import com.intellij.psi.PsiLocalVariable;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.PsiMethodCallExpression;
|
||||
import com.intellij.psi.PsiModifier;
|
||||
import com.intellij.psi.PsiNewExpression;
|
||||
import com.intellij.psi.PsiReferenceExpression;
|
||||
import com.intellij.psi.PsiResourceVariable;
|
||||
import com.intellij.psi.PsiType;
|
||||
import com.intellij.psi.PsiVariable;
|
||||
import com.intellij.psi.util.InheritanceUtil;
|
||||
import com.intellij.psi.util.PropertyUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.siyeh.InspectionGadgetsBundle;
|
||||
import com.siyeh.ig.callMatcher.CallMatcher;
|
||||
import com.siyeh.ig.psiutils.ExpressionUtils;
|
||||
import com.siyeh.ig.psiutils.VariableAccessUtils;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.intellij.codeInspection.options.OptPane.checkbox;
|
||||
import static com.intellij.codeInspection.options.OptPane.pane;
|
||||
|
||||
public final class WriteOnlyObjectInspection extends AbstractBaseJavaLocalInspectionTool {
|
||||
private static final CallMatcher OBJECT_CLONE =
|
||||
CallMatcher.exactInstanceCall(CommonClassNames.JAVA_LANG_OBJECT, "clone").parameterCount(0);
|
||||
public boolean ignoreImpureConstructors = true;
|
||||
|
||||
@Override
|
||||
public @NotNull OptPane getOptionsPane() {
|
||||
return pane(
|
||||
checkbox("ignoreImpureConstructors", InspectionGadgetsBundle.message("write.only.object.option.ignore.impure.constructors")));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
|
||||
boolean isOnTheFly) {
|
||||
return new JavaElementVisitor() {
|
||||
@Override
|
||||
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
if (!(variable instanceof PsiResourceVariable)) {
|
||||
processVariable(variable, PsiUtil.getVariableCodeBlock(variable, null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
|
||||
processVariable(field, PsiUtil.getTopLevelClass(field));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitNewExpression(@NotNull PsiNewExpression expression) {
|
||||
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(expression);
|
||||
PsiJavaCodeReferenceElement anchor = expression.getClassOrAnonymousClassReference();
|
||||
if (anchor != null && nextCall != null && isNewExpression(expression) && isWriteOnlyCall(nextCall, true)) {
|
||||
holder.registerProblem(anchor, InspectionGadgetsBundle.message("write.only.object.display.name"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression call) {
|
||||
PsiMethodCallExpression nextCall = ExpressionUtils.getCallForQualifier(call);
|
||||
PsiElement anchor = call.getMethodExpression().getReferenceNameElement();
|
||||
if (anchor != null && nextCall != null && isNewExpression(call) && isWriteOnlyCall(nextCall, false)) {
|
||||
holder.registerProblem(anchor, InspectionGadgetsBundle.message("write.only.object.display.name"));
|
||||
}
|
||||
}
|
||||
|
||||
private void processVariable(PsiVariable variable, PsiElement block) {
|
||||
if (block == null) return;
|
||||
PsiExpression initializer = PsiUtil.skipParenthesizedExprDown(variable.getInitializer());
|
||||
PsiIdentifier identifier = variable.getNameIdentifier();
|
||||
if (identifier != null && isNewExpression(initializer)) {
|
||||
boolean exactType = PsiUtil.skipParenthesizedExprDown(initializer) instanceof PsiNewExpression;
|
||||
PsiType type = variable.getType();
|
||||
if (!(type instanceof PsiClassType)) return;
|
||||
if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_COLLECTION) ||
|
||||
InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_UTIL_MAP) ||
|
||||
InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ABSTRACT_STRING_BUILDER)) {
|
||||
// will be processed by other inspections
|
||||
return;
|
||||
}
|
||||
List<PsiReferenceExpression> references = VariableAccessUtils.getVariableReferences(variable, block);
|
||||
if (references.isEmpty()) return;
|
||||
for (PsiReferenceExpression ref : references) {
|
||||
if (!isWriteWithoutRead(ref, exactType)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
holder.registerProblem(identifier, InspectionGadgetsBundle.message("write.only.object.display.name"));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static boolean isWriteWithoutRead(@NotNull PsiReferenceExpression ref, boolean exactType) {
|
||||
PsiElement parent = ref.getParent();
|
||||
if (parent instanceof PsiReferenceExpression && PsiUtil.isAccessedForWriting((PsiExpression)parent)) {
|
||||
PsiElement grandParent = parent.getParent();
|
||||
if (grandParent instanceof PsiExpression &&
|
||||
ExpressionUtils.isVoidContext((PsiExpression)grandParent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
PsiMethodCallExpression call = ExpressionUtils.getCallForQualifier(ref);
|
||||
return isWriteOnlyCall(call, exactType);
|
||||
}
|
||||
|
||||
@Contract("null, _ -> false")
|
||||
private static boolean isWriteOnlyCall(@Nullable PsiMethodCallExpression call, boolean exactType) {
|
||||
while (call != null) {
|
||||
if (!isMutatorMethod(call, exactType)) break;
|
||||
if (ExpressionUtils.isVoidContext(call)) return true;
|
||||
ContractReturnValue value = JavaMethodContractUtil.getNonFailingReturnValue(JavaMethodContractUtil.getMethodCallContracts(call));
|
||||
if (!ContractReturnValue.returnThis().equals(value)) break;
|
||||
call = ExpressionUtils.getCallForQualifier(call);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMutatorMethod(PsiMethodCallExpression call, boolean exactType) {
|
||||
MutationSignature sig = MutationSignature.fromCall(call);
|
||||
if (sig.equals(MutationSignature.pure().alsoMutatesThis())) return true;
|
||||
if (exactType) {
|
||||
PsiMethod method = call.resolveMethod();
|
||||
if (PropertyUtil.isSimpleSetter(method)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isNewExpression(PsiExpression initializer) {
|
||||
if (initializer == null) return false;
|
||||
return ExpressionUtils.nonStructuralChildren(initializer).allMatch(
|
||||
expr -> {
|
||||
if (OBJECT_CLONE.matches(expr)) return true;
|
||||
if (expr instanceof PsiNewExpression ||
|
||||
expr instanceof PsiMethodCallExpression &&
|
||||
ContractReturnValue.returnNew().equals(JavaMethodContractUtil.getNonFailingReturnValue(
|
||||
JavaMethodContractUtil.getMethodCallContracts((PsiMethodCallExpression)expr)))) {
|
||||
PsiMethod method = ((PsiCallExpression)expr).resolveMethod();
|
||||
if (method != null && Mutability.getMutability(method).isUnmodifiable()) {
|
||||
// Probably we are expecting some exception when calling the modification method
|
||||
return false;
|
||||
}
|
||||
if (ignoreImpureConstructors) {
|
||||
return MutationSignature.fromCall((PsiCallExpression)expr).isPure();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user