cleanup IDEA-382376: move com.siyeh.ig.bugs package from java-impl to java-impl-inspections

GitOrigin-RevId: 0d2e3640f64ba91bcc6869a430e45e52d01a2e2e
This commit is contained in:
Bartek Pacia
2026-02-12 12:49:55 +00:00
committed by intellij-monorepo-bot
parent b7ee3ca172
commit b924d3dcc4
11 changed files with 0 additions and 0 deletions
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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);
}
}
}
@@ -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;
}
}
}
@@ -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();
}
};
}
}
@@ -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;
});
}
}