[java-highlighting] SafeVarargs checks -> AnnotationChecker

Also, warnings are moved out to inspections
Part of IDEA-365344 Create a new Java error highlighter with minimal dependencies (PSI only)

GitOrigin-RevId: 2292723fde787454467307c213ea38e730aa0ff5
This commit is contained in:
Tagir Valeev
2025-01-07 12:13:37 +01:00
committed by intellij-monorepo-bot
parent 6a2b751ffd
commit c7edfdbe4a
18 changed files with 250 additions and 151 deletions

View File

@@ -15,3 +15,10 @@ lambda.not.a.functional.interface={0} is not a functional interface
lambda.no.target.method.found=No target method found
lambda.multiple.sam.candidates=Multiple non-overriding abstract methods found in interface {0}
lambda.sealed.functional.interface=Functional interface can't be declared as 'sealed'
safe.varargs.on.record.component=@SafeVarargs is not allowed on a record component
safe.varargs.on.fixed.arity=@SafeVarargs is not allowed on methods with fixed arity
safe.varargs.on.non.final.method=@SafeVarargs is not allowed on non-final instance methods
override.on.static.method=Static methods cannot be annotated with @Override
override.on.non-overriding.method=Method does not override method from its superclass

View File

@@ -7,7 +7,11 @@ import com.intellij.openapi.util.Comparing;
import com.intellij.patterns.ElementPattern;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.impl.IncompleteModelUtil;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.util.JavaPsiRecordUtil;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Contract;
@@ -72,8 +76,10 @@ final class AnnotationChecker {
if (!myVisitor.hasErrorResults()) checkAnnotationApplicability(annotation);
if (!myVisitor.hasErrorResults()) checkAnnotationType(annotation);
if (!myVisitor.hasErrorResults()) checkMissingAttributes(annotation);
if (!myVisitor.hasErrorResults()) checkSafeVarargAnnotation(annotation);
if (!myVisitor.hasErrorResults()) checkTargetAnnotationDuplicates(annotation);
if (!myVisitor.hasErrorResults()) checkFunctionalInterface(annotation);
if (!myVisitor.hasErrorResults()) checkOverrideAnnotation(annotation);
}
private void checkFunctionalInterface(@NotNull PsiAnnotation annotation) {
@@ -96,6 +102,23 @@ final class AnnotationChecker {
}
}
private void checkSafeVarargAnnotation(@NotNull PsiAnnotation annotation) {
if (!Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_SAFE_VARARGS)) return;
PsiAnnotationOwner owner = annotation.getOwner();
if (!(owner instanceof PsiModifierList list)) return;
PsiElement parent = list.getParent();
if (parent instanceof PsiRecordComponent) {
myVisitor.report(JavaErrorKinds.SAFE_VARARGS_ON_RECORD_COMPONENT.create(annotation));
} else if (parent instanceof PsiMethod method) {
if (!method.isVarArgs()) {
myVisitor.report(JavaErrorKinds.SAFE_VARARGS_ON_FIXED_ARITY.create(annotation, method));
}
else if (!GenericsUtil.isSafeVarargsNoOverridingCondition(method)) {
myVisitor.report(JavaErrorKinds.SAFE_VARARGS_ON_NON_FINAL_METHOD.create(annotation, method));
}
}
}
private void checkMissingAttributes(@NotNull PsiAnnotation annotation) {
PsiJavaCodeReferenceElement nameRef = annotation.getNameReferenceElement();
if (nameRef == null) return;
@@ -236,4 +259,50 @@ final class AnnotationChecker {
}
}
}
private void checkOverrideAnnotation(@NotNull PsiAnnotation annotation) {
if (!CommonClassNames.JAVA_LANG_OVERRIDE.equals(annotation.getQualifiedName())) return;
if (!(annotation.getOwner() instanceof PsiModifierList list)) return;
if (!(list.getParent() instanceof PsiMethod method)) return;
if (method.hasModifierProperty(PsiModifier.STATIC)) {
myVisitor.report(JavaErrorKinds.OVERRIDE_ON_STATIC_METHOD.create(annotation, method));
}
MethodSignatureBackedByPsiMethod superMethod = SuperMethodsSearch.search(method, null, true, false).findFirst();
PsiClass psiClass = method.getContainingClass();
if (psiClass != null) {
if (superMethod != null && psiClass.isInterface()) {
PsiMethod psiMethod = superMethod.getMethod();
PsiClass superClass = psiMethod.getContainingClass();
if (superClass != null &&
CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()) &&
psiMethod.hasModifierProperty(PsiModifier.PROTECTED)) {
superMethod = null;
}
}
else if (superMethod == null) {
if (IncompleteModelUtil.isIncompleteModel(psiClass)) {
if (!IncompleteModelUtil.isHierarchyResolved(psiClass)) {
return;
}
}
else {
for (PsiClassType type : psiClass.getSuperTypes()) {
// There's an unresolvable superclass: likely the error on @Override is induced.
// Do not show an error on override, as it's reasonable to fix hierarchy first.
if (type.resolve() == null) return;
}
}
}
}
if (superMethod == null) {
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) == null) {
myVisitor.report(JavaErrorKinds.OVERRIDE_ON_NON_OVERRIDING_METHOD.create(annotation, method));
}
return;
}
PsiClass superClass = superMethod.getMethod().getContainingClass();
if (superClass != null && superClass.isInterface()) {
myVisitor.checkFeature(annotation, JavaFeature.OVERRIDE_INTERFACE);
}
}
}

View File

@@ -6,10 +6,7 @@ import com.intellij.java.codeserver.highlighting.errors.JavaErrorKinds;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.JavaFeature;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
@@ -59,6 +56,28 @@ final class JavaErrorVisitor extends JavaElementVisitor {
myAnnotationChecker.checkAnnotation(annotation);
}
@Override
public void visitNameValuePair(@NotNull PsiNameValuePair pair) {
super.visitNameValuePair(pair);
myAnnotationChecker.checkNameValuePair(pair);
}
@Override
public void visitAnnotationArrayInitializer(@NotNull PsiArrayInitializerMemberValue initializer) {
super.visitAnnotationArrayInitializer(initializer);
myAnnotationChecker.checkArrayInitializer(initializer);
}
@Override
public void visitAnnotationMethod(@NotNull PsiAnnotationMethod method) {
super.visitAnnotationMethod(method);
PsiType returnType = method.getReturnType();
PsiAnnotationMemberValue value = method.getDefaultValue();
if (returnType != null && value != null) {
myAnnotationChecker.checkMemberValueType(value, returnType, method);
}
}
void checkFeature(@NotNull PsiElement element, @NotNull JavaFeature feature) {
if (!feature.isSufficient(myLanguageLevel)) {
report(JavaErrorKinds.UNSUPPORTED_FEATURE.create(element, feature));

View File

@@ -56,7 +56,7 @@ public final class JavaErrorKinds {
};
// Can be anchored on @FunctionalInterface annotation or at call site
public static final JavaErrorKind<PsiElement, PsiClass> LAMBDA_NO_TARGET_METHOD =
new Parameterized<>("lambda.no.target.method.found") {};
new Parameterized<>("lambda.no.target.method.found");
// Can be anchored on @FunctionalInterface annotation or at call site
public static final JavaErrorKind<PsiElement, PsiClass> LAMBDA_MULTIPLE_TARGET_METHODS =
new Parameterized<>("lambda.multiple.sam.candidates") {
@@ -66,7 +66,7 @@ public final class JavaErrorKinds {
}
};
public static final JavaErrorKind<PsiAnnotation, PsiClass> LAMBDA_FUNCTIONAL_INTERFACE_SEALED =
new Parameterized<>("lambda.sealed.functional.interface") {};
new Parameterized<>("lambda.sealed.functional.interface");
public static final JavaErrorKind<PsiAnnotation, @NotNull List<PsiAnnotation.@NotNull TargetType>> ANNOTATION_NOT_APPLICABLE =
new Parameterized<>("annotation.not.applicable") {
@Override
@@ -98,4 +98,14 @@ public final class JavaErrorKinds {
"annotation.missing.attribute", attributeNames.stream().map(attr -> "'" + attr + "'").collect(Collectors.joining(", "))));
}
};
public static final JavaSimpleErrorKind<PsiAnnotation> SAFE_VARARGS_ON_RECORD_COMPONENT =
new JavaSimpleErrorKind<>("safe.varargs.on.record.component");
public static final JavaErrorKind<PsiAnnotation, PsiMethod> SAFE_VARARGS_ON_FIXED_ARITY =
new Parameterized<>("safe.varargs.on.fixed.arity");
public static final JavaErrorKind<PsiAnnotation, PsiMethod> SAFE_VARARGS_ON_NON_FINAL_METHOD =
new Parameterized<>("safe.varargs.on.non.final.method");
public static final JavaErrorKind<PsiAnnotation, PsiMethod> OVERRIDE_ON_STATIC_METHOD =
new Parameterized<>("override.on.static.method");
public static final JavaErrorKind<PsiAnnotation, PsiMethod> OVERRIDE_ON_NON_OVERRIDING_METHOD =
new Parameterized<>("override.on.non-overriding.method");
}

View File

@@ -7,7 +7,7 @@ import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;
non-sealed abstract class Parameterized<Psi extends PsiElement, Context> implements JavaErrorKind<Psi, Context> {
non-sealed class Parameterized<Psi extends PsiElement, Context> implements JavaErrorKind<Psi, Context> {
private final @NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String myKey;
Parameterized(@NotNull @PropertyKey(resourceBundle = JavaCompilationErrorBundle.BUNDLE) String key) {

View File

@@ -611,3 +611,5 @@ inspection.message.expression.compared.to.itself.description=Expression is compa
intention.name.do.not.report.conditions.with.possible.side.effect=Do not report conditions with possible side-effect
dfa.find.cause.or.another=or {0}
dfa.find.cause.and.another=and {0}
safe.varargs.not.suppress.potentially.unsafe.operations=@SafeVarargs do not suppress potentially unsafe operations
safe.varargs.on.reifiable.type=@SafeVarargs is not applicable for reifiable types

View File

@@ -134,6 +134,16 @@
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.PossibleHeapPollutionVarargsInspection"
key="inspection.safe.varargs.detector.display.name" bundle="messages.JavaAnalysisBundle"/>
<localInspection groupPath="Java" language="JAVA" shortName="SafeVarargsHasNoEffect"
groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.SafeVarargsHasNoEffectInspection"
key="safe.varargs.not.suppress.potentially.unsafe.operations" bundle="messages.JavaAnalysisBundle"/>
<localInspection groupPath="Java" language="JAVA" shortName="SafeVarargsOnNonReifiableType"
groupKey="group.names.declaration.redundancy" groupBundle="messages.InspectionsBundle"
enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.SafeVarargsOnNonReifiableTypeInspection"
key="safe.varargs.on.reifiable.type" bundle="messages.JavaAnalysisBundle"/>
<localInspection groupPathKey="group.path.names.java.language.level.specific.issues.and.migration.aids" language="JAVA" shortName="CodeBlock2Expr"
groupKey="group.names.language.level.specific.issues.and.migration.aids8" groupBundle="messages.InspectionsBundle" enabledByDefault="true" level="WARNING"
implementationClass="com.intellij.codeInspection.RedundantLambdaCodeBlockInspection"

View File

@@ -14,7 +14,6 @@ import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
import com.intellij.core.JavaPsiBundle;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.pom.java.LanguageLevel;
@@ -434,16 +433,6 @@ public final class AnnotationsHighlightUtil {
return null;
}
static HighlightInfo.Builder checkInvalidAnnotationOnRecordComponent(@NotNull PsiAnnotation annotation) {
if (!Comparing.strEqual(annotation.getQualifiedName(), CommonClassNames.JAVA_LANG_SAFE_VARARGS)) return null;
PsiAnnotationOwner owner = annotation.getOwner();
if (!(owner instanceof PsiModifierList list)) return null;
PsiElement parent = list.getParent();
if (!(parent instanceof PsiRecordComponent)) return null;
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(annotation)
.descriptionAndTooltip(JavaErrorBundle.message("safevararg.annotation.cannot.be.applied.for.record.component"));
}
static HighlightInfo.Builder checkRepeatableAnnotation(@NotNull PsiAnnotation annotation) {
String qualifiedName = annotation.getQualifiedName();
if (!CommonClassNames.JAVA_LANG_ANNOTATION_REPEATABLE.equals(qualifiedName)) return null;

View File

@@ -12,7 +12,6 @@ import com.intellij.core.JavaPsiBundle;
import com.intellij.ide.IdeBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.IndexNotReadyException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.JavaSdkVersion;
import com.intellij.openapi.projectRoots.JavaVersionService;
@@ -30,13 +29,11 @@ import com.intellij.psi.impl.PsiClassImplUtil;
import com.intellij.psi.impl.PsiImplUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.search.searches.SuperMethodsSearch;
import com.intellij.psi.util.*;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.psiutils.InstanceOfUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -1115,123 +1112,6 @@ public final class GenericsHighlightUtil {
return null;
}
static HighlightInfo.Builder checkOverrideAnnotation(@NotNull PsiMethod method,
@NotNull PsiAnnotation overrideAnnotation,
@NotNull LanguageLevel languageLevel) {
if (method.hasModifierProperty(PsiModifier.STATIC)) {
QuickFixFactory factory = QuickFixFactory.getInstance();
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(overrideAnnotation)
.registerFix(factory.createDeleteFix(overrideAnnotation), null, null, null, null)
.descriptionAndTooltip(
JavaErrorBundle.message("static.method.cannot.be.annotated.with.override"));
}
try {
MethodSignatureBackedByPsiMethod superMethod = SuperMethodsSearch.search(method, null, true, false).findFirst();
PsiClass psiClass = method.getContainingClass();
if (psiClass != null) {
if (superMethod != null && psiClass.isInterface()) {
PsiMethod psiMethod = superMethod.getMethod();
PsiClass superClass = psiMethod.getContainingClass();
if (superClass != null &&
CommonClassNames.JAVA_LANG_OBJECT.equals(superClass.getQualifiedName()) &&
psiMethod.hasModifierProperty(PsiModifier.PROTECTED)) {
superMethod = null;
}
} else if (superMethod == null) {
if (IncompleteModelUtil.isIncompleteModel(psiClass)) {
if (!IncompleteModelUtil.isHierarchyResolved(psiClass)) {
return null;
}
} else {
for (PsiClassType type : psiClass.getSuperTypes()) {
// There's an unresolvable superclass: likely the error on @Override is induced.
// Do not show an error on override, as it's reasonable to fix hierarchy first.
if (type.resolve() == null) return null;
}
}
}
}
if (superMethod == null) {
if (JavaPsiRecordUtil.getRecordComponentForAccessor(method) != null) {
return null;
}
String description = JavaErrorBundle.message("method.does.not.override.super");
HighlightInfo.Builder builder =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(overrideAnnotation).descriptionAndTooltip(description);
List<IntentionAction> registrar = new ArrayList<>();
QuickFixFactory factory = QuickFixFactory.getInstance();
factory.registerPullAsAbstractUpFixes(method, registrar);
for (IntentionAction action : registrar) {
builder.registerFix(action, null, null, null, null);
}
builder.registerFix(factory.createDeleteFix(overrideAnnotation), null, null, null, null);
return builder;
}
PsiClass superClass = superMethod.getMethod().getContainingClass();
if (superClass != null && superClass.isInterface()) {
return HighlightUtil.checkFeature(overrideAnnotation, JavaFeature.OVERRIDE_INTERFACE, languageLevel,
overrideAnnotation.getContainingFile());
}
return null;
}
catch (IndexNotReadyException e) {
return null;
}
}
static HighlightInfo.Builder checkSafeVarargsAnnotation(@NotNull PsiMethod method, @NotNull LanguageLevel languageLevel) {
PsiModifierList list = method.getModifierList();
PsiAnnotation safeVarargsAnnotation = list.findAnnotation(CommonClassNames.JAVA_LANG_SAFE_VARARGS);
if (safeVarargsAnnotation == null) {
return null;
}
try {
if (!method.isVarArgs()) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(safeVarargsAnnotation).descriptionAndTooltip(
JavaErrorBundle.message("safevarargs.not.allowed.on.methods.with.fixed.arity"));
}
if (!isSafeVarargsNoOverridingCondition(method, languageLevel)) {
HighlightInfo.Builder info =
HighlightInfo.newHighlightInfo(HighlightInfoType.ERROR).range(safeVarargsAnnotation).descriptionAndTooltip(
JavaErrorBundle.message("safevarargs.not.allowed.non.final.instance.methods"));
IntentionAction action = QuickFixFactory.getInstance().createModifierListFix(method, PsiModifier.FINAL, true, true);
info.registerFix(action, null, null, null, null);
return info;
}
PsiParameterList parameterList = method.getParameterList();
PsiParameter varParameter = Objects.requireNonNull(parameterList.getParameter(parameterList.getParametersCount() - 1));
for (PsiReferenceExpression element : VariableAccessUtils.getVariableReferences(varParameter, method.getBody())) {
if (!PsiUtil.isAccessedForReading(element)) {
return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(element).descriptionAndTooltip(
JavaErrorBundle.message("safevarargs.not.suppress.potentially.unsafe.operations"));
}
}
LOG.assertTrue(varParameter.isVarArgs());
PsiEllipsisType ellipsisType = (PsiEllipsisType)varParameter.getType();
PsiType componentType = ellipsisType.getComponentType();
if (JavaGenericsUtil.isReifiableType(componentType)) {
PsiElement element = varParameter.getTypeElement();
return HighlightInfo.newHighlightInfo(HighlightInfoType.WARNING).range(element).descriptionAndTooltip(
JavaErrorBundle.message("safevarargs.not.applicable.for.reifiable.types"));
}
return null;
}
catch (IndexNotReadyException e) {
return null;
}
}
public static boolean isSafeVarargsNoOverridingCondition(@NotNull PsiMethod method, @NotNull LanguageLevel languageLevel) {
return method.hasModifierProperty(PsiModifier.FINAL) ||
method.hasModifierProperty(PsiModifier.STATIC) ||
method.isConstructor() ||
method.hasModifierProperty(PsiModifier.PRIVATE) && languageLevel.isAtLeast(LanguageLevel.JDK_1_9);
}
static void checkEnumConstantForConstructorProblems(@NotNull Project project,
@NotNull PsiEnumConstant enumConstant,
@NotNull JavaSdkVersion javaSdkVersion, @NotNull Consumer<? super HighlightInfo.Builder> errorSink) {

View File

@@ -245,15 +245,7 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
public void visitAnnotation(@NotNull PsiAnnotation annotation) {
super.visitAnnotation(annotation);
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkDuplicateAnnotations(annotation, myLanguageLevel));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkInvalidAnnotationOnRecordComponent(annotation));
if (!hasErrorResults()) add(AnnotationsHighlightUtil.checkRepeatableAnnotation(annotation));
if (CommonClassNames.JAVA_LANG_OVERRIDE.equals(annotation.getQualifiedName())) {
PsiAnnotationOwner owner = annotation.getOwner();
PsiElement parent = owner instanceof PsiModifierList list ? list.getParent() : null;
if (parent instanceof PsiMethod psiMethod) {
add(GenericsHighlightUtil.checkOverrideAnnotation(psiMethod, annotation, myLanguageLevel));
}
}
}
private boolean add(@Nullable HighlightInfo.Builder builder) {
@@ -938,7 +930,6 @@ public class HighlightVisitorImpl extends JavaElementVisitor implements Highligh
super.visitMethod(method);
if (!hasErrorResults()) add(HighlightControlFlowUtil.checkUnreachableStatement(method.getBody()));
if (!hasErrorResults()) add(HighlightMethodUtil.checkConstructorHandleSuperClassExceptions(method));
if (!hasErrorResults()) add(GenericsHighlightUtil.checkSafeVarargsAnnotation(method, myLanguageLevel));
if (!hasErrorResults()) add(HighlightMethodUtil.checkRecordAccessorDeclaration(method));
if (!hasErrorResults()) HighlightMethodUtil.checkRecordConstructorDeclaration(method, myErrorSink);

View File

@@ -40,6 +40,13 @@ final class JavaErrorFixProvider {
private static final Map<JavaErrorKind<?, ?>, List<JavaFixesProvider<?, ?>>> FIXES = new HashMap<>();
static {
single(SAFE_VARARGS_ON_NON_FINAL_METHOD,
error -> QuickFixFactory.getInstance().createModifierListFix(error.context(), PsiModifier.FINAL, true, true));
multi(OVERRIDE_ON_NON_OVERRIDING_METHOD, error -> {
List<CommonIntentionAction> registrar = new ArrayList<>();
QuickFixFactory.getInstance().registerPullAsAbstractUpFixes(error.context(), registrar);
return registrar;
});
JavaFixProvider<PsiElement, Object> annotationRemover = error ->
error.psi() instanceof PsiAnnotation annotation ? QuickFixFactory.getInstance()
.createDeleteFix(annotation, JavaAnalysisBundle.message("intention.text.remove.annotation")) : null;
@@ -47,7 +54,9 @@ final class JavaErrorFixProvider {
ANNOTATION_NOT_ALLOWED_REF, ANNOTATION_NOT_ALLOWED_VAR,
ANNOTATION_NOT_ALLOWED_VOID, LAMBDA_MULTIPLE_TARGET_METHODS, LAMBDA_NO_TARGET_METHOD,
LAMBDA_NOT_FUNCTIONAL_INTERFACE, ANNOTATION_NOT_APPLICABLE,
LAMBDA_FUNCTIONAL_INTERFACE_SEALED)) {
LAMBDA_FUNCTIONAL_INTERFACE_SEALED, OVERRIDE_ON_STATIC_METHOD,
OVERRIDE_ON_NON_OVERRIDING_METHOD, SAFE_VARARGS_ON_FIXED_ARITY,
SAFE_VARARGS_ON_NON_FINAL_METHOD, SAFE_VARARGS_ON_RECORD_COMPONENT)) {
single(kind, annotationRemover);
}
single(ANNOTATION_NOT_ALLOWED_VAR, error -> {

View File

@@ -2,7 +2,6 @@
package com.intellij.codeInspection;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.impl.analysis.GenericsHighlightUtil;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.modcommand.ModPsiUpdater;
@@ -154,7 +153,8 @@ public final class PossibleHeapPollutionVarargsInspection extends AbstractBaseJa
protected void registerProblem(PsiMethod method, PsiIdentifier nameIdentifier) {
final LocalQuickFix quickFix;
if (GenericsHighlightUtil.isSafeVarargsNoOverridingCondition(method, PsiUtil.getLanguageLevel(method))) {
PsiUtil.getLanguageLevel(method);
if (GenericsUtil.isSafeVarargsNoOverridingCondition(method)) {
quickFix = new AnnotateAsSafeVarargsQuickFix(false);
}
else {

View File

@@ -0,0 +1,32 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
public final class SafeVarargsHasNoEffectInspection extends AbstractBaseJavaLocalInspectionTool {
@Override
public @NotNull Set<@NotNull JavaFeature> requiredFeatures() {
return Set.of(JavaFeature.ANNOTATIONS);
}
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
if (PsiUtil.isAccessedForReading(expression)) return;
if (!(expression.resolve() instanceof PsiParameter parameter)) return;
if (!(parameter.getDeclarationScope() instanceof PsiMethod method)) return;
if (!(parameter.getType() instanceof PsiEllipsisType)) return;
if (!method.getModifierList().hasAnnotation(CommonClassNames.JAVA_LANG_SAFE_VARARGS)) return;
holder.registerProblem(expression, JavaAnalysisBundle.message("safe.varargs.not.suppress.potentially.unsafe.operations"));
}
};
}
}

View File

@@ -0,0 +1,38 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection;
import com.intellij.codeInsight.daemon.impl.analysis.JavaGenericsUtil;
import com.intellij.java.analysis.JavaAnalysisBundle;
import com.intellij.pom.java.JavaFeature;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.Set;
public final class SafeVarargsOnNonReifiableTypeInspection extends AbstractBaseJavaLocalInspectionTool {
@Override
public @NotNull Set<@NotNull JavaFeature> requiredFeatures() {
return Set.of(JavaFeature.ANNOTATIONS);
}
@Override
public @NotNull PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
return new JavaElementVisitor() {
@Override
public void visitParameter(@NotNull PsiParameter parameter) {
super.visitParameter(parameter);
if (!parameter.isVarArgs()) return;
if (!(parameter.getDeclarationScope() instanceof PsiMethod method)) return;
if (!method.getModifierList().hasAnnotation(CommonClassNames.JAVA_LANG_SAFE_VARARGS)) return;
if (!method.isVarArgs() || !GenericsUtil.isSafeVarargsNoOverridingCondition(method)) return;
PsiEllipsisType ellipsisType = (PsiEllipsisType)parameter.getType();
PsiType componentType = ellipsisType.getComponentType();
if (JavaGenericsUtil.isReifiableType(componentType)) {
PsiTypeElement element = Objects.requireNonNull(parameter.getTypeElement());
holder.registerProblem(element, JavaAnalysisBundle.message("safe.varargs.on.reifiable.type"));
}
}
};
}
}

View File

@@ -0,0 +1,15 @@
<html>
<body>
Reports write operations inside <code>@SafeVarargs</code> methods where vararg parameter is reassigned to something else.
In this case, newly assigned array is not related to the variable arity arguments at the call site and might not be safe anymore.
<p><b>Example:</b></p>
<pre><code>
@SafeVarargs
public final &lt;T&gt; void test(T[] tt, T... t) {
t = tt;
System.out.println(t[0]);
}
</code></pre>
<!-- tooltip end -->
</body>
</html>

View File

@@ -0,0 +1,13 @@
<html>
<body>
Reports reifiable type of variable arity method annotated with <code>@SafeVarargs</code>.
In this case, the annotation is redundant.
<p><b>Example:</b></p>
<pre><code>
@SafeVarargs
public final void processStrings(String... strings) {
}
</code></pre>
<!-- tooltip end -->
</body>
</html>

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Couple;
import com.intellij.openapi.util.Pair;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.*;
import com.intellij.util.ObjectUtils;
@@ -646,4 +647,15 @@ public final class GenericsUtil {
return JavaPsiFacade.getElementFactory(target.getProject())
.createType(target, parameters).annotate(classType.getAnnotationProvider());
}
/**
* @param method method to check
* @return true if a given method can be annotated as safe-varargs according to its overridability
*/
public static boolean isSafeVarargsNoOverridingCondition(@NotNull PsiMethod method) {
return method.hasModifierProperty(PsiModifier.FINAL) ||
method.hasModifierProperty(PsiModifier.STATIC) ||
method.isConstructor() ||
method.hasModifierProperty(PsiModifier.PRIVATE) && PsiUtil.getLanguageLevel(method).isAtLeast(LanguageLevel.JDK_1_9);
}
}

View File

@@ -4,6 +4,8 @@ package com.intellij.java.codeInsight.daemon;
import com.intellij.codeInsight.daemon.LightDaemonAnalyzerTestCase;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.codeInspection.SafeVarargsHasNoEffectInspection;
import com.intellij.codeInspection.SafeVarargsOnNonReifiableTypeInspection;
import com.intellij.codeInspection.compiler.JavacQuirksInspection;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspectionBase;
@@ -35,7 +37,8 @@ public class LightAdvHighlightingJdk7Test extends LightDaemonAnalyzerTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
enableInspectionTools(new UnusedDeclarationInspection(), new UncheckedWarningLocalInspection(), new JavacQuirksInspection(), new RedundantCastInspection());
enableInspectionTools(new UnusedDeclarationInspection(), new UncheckedWarningLocalInspection(), new JavacQuirksInspection(), new RedundantCastInspection(),
new SafeVarargsHasNoEffectInspection(), new SafeVarargsOnNonReifiableTypeInspection());
setLanguageLevel(LanguageLevel.JDK_1_7);
IdeaTestUtil.setTestVersion(JavaSdkVersion.JDK_1_7, getModule(), getTestRootDisposable());
}