[java-dfa] Do not assume that fields are non-initialized in readResolve() when readObject() is present

Fixes IDEA-351947 False positive warning 'Nullability and data flow problems' in readResolve

GitOrigin-RevId: 26c71c3e26c087334741a5a098812f91144bdcd4
This commit is contained in:
Tagir Valeev
2024-04-17 14:20:27 +00:00
committed by intellij-monorepo-bot
parent 2c419527be
commit ff1cfed0de
3 changed files with 41 additions and 3 deletions
@@ -18,6 +18,8 @@ import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.JavaPsiConstructorUtil;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.siyeh.ig.callMatcher.CallMatcher;
import com.siyeh.ig.psiutils.ExpressionUtils;
import com.siyeh.ig.psiutils.VariableAccessUtils;
import org.jetbrains.annotations.NotNull;
@@ -159,16 +161,20 @@ public final class PlainDescriptor extends PsiVarDescriptor {
if (!placeMethod.hasModifierProperty(PsiModifier.STATIC) && target.hasModifierProperty(PsiModifier.STATIC)) return false;
if (!target.hasModifierProperty(PsiModifier.STATIC) &&
!placeMethod.hasModifierProperty(PsiModifier.STATIC) &&
(isReadResolveMethod(placeMethod, placeClass) ||
(isReadResolveMethodWithoutReadObject(placeMethod, placeClass) ||
methodCanBeCalledFromConstructorBeforeFieldInitializing(target, placeMethod, placeClass))) {
return true;
}
return getAccessOffset(placeMethod) < getWriteOffset(target);
}
private static boolean isReadResolveMethod(@NotNull PsiMethod method, @NotNull PsiClass aClass) {
private static final CallMatcher READ_OBJECT = CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_OBJECT, "readObject")
.parameterTypes("java.io.ObjectInputStream");
private static boolean isReadResolveMethodWithoutReadObject(@NotNull PsiMethod method, @NotNull PsiClass aClass) {
return "readResolve".equals(method.getName()) && method.getParameterList().isEmpty() &&
InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_IO_SERIALIZABLE);
!aClass.isRecord() && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_IO_SERIALIZABLE) &&
!ContainerUtil.exists(aClass.findMethodsByName("readObject", false), READ_OBJECT::methodMatches);
}
private record TargetCallInfo(@NotNull PsiMethod constructor,
@@ -0,0 +1,31 @@
import org.jetbrains.annotations.NotNull;
import java.io.*;
import java.util.Base64;
class Test implements Serializable {
@NotNull String string;
Test(@NotNull String string) {
this.string = string;
}
@Serial
private void readObject(ObjectInputStream in) throws IOException {
this.string = in.readUTF();
}
@Serial
protected Object readResolve() {
if (<warning descr="Condition 'string == null' is always 'false'">string == null</warning>) {
throw new IllegalStateException("Wrong object!");
}
return this;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
byte[] data = Base64.getDecoder().decode("rO0ABXNyAARUZXN0ST9d8XKvH/0CAAFMAAZzdHJpbmd0ABJMamF2YS9sYW5nL1N0cmluZzt4cHA=");
Test object = (Test) new ObjectInputStream(new ByteArrayInputStream(data)).readObject();
System.out.println(object);
}
}
@@ -114,6 +114,7 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
doTest();
}
public void testReadResolve() { doTest(); }
public void testReadResolve2() { doTest(); }
public void testDifferentTypesButNullable() { doTest(); }
public void testForEachPattern() {