new "Serializable object implicitly stores non-Serializable object" inspection IDEA-62336

This commit is contained in:
Bas Leijdekkers
2014-05-23 16:02:25 +02:00
parent 9db4420563
commit ec32500375
6 changed files with 202 additions and 1 deletions
@@ -2214,6 +2214,10 @@
key="serializable.inner.class.with.non.serializable.outer.class.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.serialization.issues" enabledByDefault="false" level="WARNING"
implementationClass="com.siyeh.ig.serialization.SerializableInnerClassWithNonSerializableOuterClassInspection"/>
<localInspection language="JAVA" shortName="SerializableStoresNonSerializable" bundle="com.siyeh.InspectionGadgetsBundle"
key="serializable.stores.non.serializable.display.name" groupBundle="messages.InspectionsBundle"
groupKey="group.names.serialization.issues" enabledByDefault="false" level="WARNING"
implementationClass="com.siyeh.ig.serialization.SerializableStoresNonSerializableInspection"/>
<localInspection language="JAVA" suppressId="SerializableClassWithUnconstructableAncestor" shortName="SerializableWithUnconstructableAncestor"
bundle="com.siyeh.InspectionGadgetsBundle" key="serializable.with.unconstructable.ancestor.display.name"
groupBundle="messages.InspectionsBundle" groupKey="group.names.serialization.issues" enabledByDefault="false"
@@ -2088,4 +2088,8 @@ big.decimal.method.without.rounding.called.display.name=Call to 'BigDecimal' met
big.decimal.method.without.rounding.called.problem.descriptor='BigDecimal.#ref()' called without a rounding mode argument
bigdecimal.legacy.method.display.name='BigDecimal' legacy method called
bigdecimal.legacy.method.problem.descriptor=Call to 'BigDecimal.#ref()' can use 'RoundingMode' enum constant
bigdecimal.legacy.method.quickfix=Use 'RoundingMode' enum constant
bigdecimal.legacy.method.quickfix=Use 'RoundingMode' enum constant
serializable.stores.non.serializable.display.name=Serializable object implicitly stores non-Serializable object
serializable.lambda.stores.non.serializable.problem.descriptor=Serializable lambda implicitly stores non-Serializable object of type ''{0}''
serializable.local.class.stores.non.serializable.problem.descriptor=Serializable local class ''{1}'' implicitly stores non-Serializable object of type ''{0}''
serializable.anonymous.class.stores.non.serializable.problem.descriptor=Serializable anonymous class implicitly stores non-Serializable object of type ''{0}''
@@ -0,0 +1,121 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.serialization;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightControlFlowUtil;
import com.intellij.psi.*;
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.SerializationUtils;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
/**
* @author Bas Leijdekkers
*/
public class SerializableStoresNonSerializableInspection extends BaseInspection {
@Nls
@NotNull
@Override
public String getDisplayName() {
return InspectionGadgetsBundle.message("serializable.stores.non.serializable.display.name");
}
@NotNull
@Override
protected String buildErrorString(Object... infos) {
final PsiElement classOrLambda = (PsiElement)infos[0];
final PsiType type = (PsiType)infos[1];
if (classOrLambda instanceof PsiClass) {
final PsiClass aClass = (PsiClass)classOrLambda;
if (aClass instanceof PsiAnonymousClass) {
return InspectionGadgetsBundle.message("serializable.anonymous.class.stores.non.serializable.problem.descriptor",
type.getPresentableText());
}
else {
return InspectionGadgetsBundle.message("serializable.local.class.stores.non.serializable.problem.descriptor",
type.getPresentableText(), aClass.getName());
}
}
return InspectionGadgetsBundle.message("serializable.lambda.stores.non.serializable.problem.descriptor", type.getPresentableText());
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new SerializableStoresNonSerializableVisitor();
}
private static class SerializableStoresNonSerializableVisitor extends BaseInspectionVisitor {
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
final PsiElement parent = aClass.getParent();
if (!(parent instanceof PsiDeclarationStatement) && !(aClass instanceof PsiAnonymousClass)) {
return;
}
final LocalVariableReferenceFinder visitor = new LocalVariableReferenceFinder(aClass);
aClass.accept(visitor);
}
@Override
public void visitLambdaExpression(PsiLambdaExpression lambda) {
super.visitLambdaExpression(lambda);
final LocalVariableReferenceFinder visitor = new LocalVariableReferenceFinder(lambda);
lambda.accept(visitor);
}
private class LocalVariableReferenceFinder extends JavaRecursiveElementWalkingVisitor {
private final PsiElement myClassOrLambda;
public LocalVariableReferenceFinder(PsiElement classOrLambda) {
myClassOrLambda = classOrLambda;
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
if (expression.getQualifierExpression() != null) {
return;
}
final PsiType type = expression.getType();
if (SerializationUtils.isProbablySerializable(type)) {
return;
}
final PsiElement target = expression.resolve();
if (!(target instanceof PsiLocalVariable) && !(target instanceof PsiParameter)) {
return;
}
final PsiVariable variable = (PsiVariable)target;
if (!variable.hasModifierProperty(PsiModifier.FINAL)) {
if (!PsiUtil.isLanguageLevel8OrHigher(variable) ||
!HighlightControlFlowUtil.isEffectivelyFinal(variable, myClassOrLambda, expression)) {
// don't warn on uncompilable code.
return;
}
}
if (PsiTreeUtil.isAncestor(myClassOrLambda, variable, true)) {
return;
}
registerError(expression, myClassOrLambda, type);
}
}
}
}
@@ -0,0 +1,11 @@
<html>
<body>
Checks lambda's, local and anonymous classes and reports references to local variables outside the class or lambda that are not
<b>Serializable</b>.
When a local variable is referenced from an anonymous class, it's value is stored in an implicit field in the anonymous class.
The same happens for local classes and lambda's.
If that variable is of a non-Serializable type, serialization will fail.
<p>
<small>New in 14</small>
</body>
</html>
@@ -0,0 +1,28 @@
class SerializableStoresNonSerializable {
void f(B b, C c) {
new A() {
@Override
public void m() {
System.out.println(c);
System.out.println(<warning descr="Serializable anonymous class implicitly stores non-Serializable object of type 'B'">b</warning>);
}
};
A a = () -> {
System.out.println(c);
System.out.println(<warning descr="Serializable lambda implicitly stores non-Serializable object of type 'B'">b</warning>);
};
class D implements A {
@Override
public void m() {
System.out.println(c);
System.out.println(<warning descr="Serializable local class 'D' implicitly stores non-Serializable object of type 'B'">b</warning>);
}
}
}
}
interface A {
void m();
}
class B {}
class C implements java.io.Serializable {}
@@ -0,0 +1,33 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.serialization;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.siyeh.ig.LightInspectionTestCase;
/**
* @author Bas Leijdekkers
*/
public class SerializableStoresNonSerializableInspectionTest extends LightInspectionTestCase {
@Override
protected InspectionProfileEntry getInspection() {
return new SerializableStoresNonSerializableInspection();
}
public void testSerializableStoresNonSerializable() {
doTest();
}
}