[java-intentions] IDEA-313717. Range for get and getLong methods of java.time TemporalAccessor

GitOrigin-RevId: c5f5b3ce075b1da349eef7695e955107bb50626c
This commit is contained in:
Mikhail Pyltsin
2023-02-20 15:51:46 +01:00
committed by intellij-monorepo-bot
parent 2f754aadf4
commit ed61db9030
4 changed files with 202 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ import com.intellij.codeInspection.dataFlow.value.DfaBinOpValue;
import com.intellij.codeInspection.dataFlow.value.DfaValue;
import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
import com.intellij.codeInspection.dataFlow.value.RelationType;
import com.intellij.codeInspection.util.ChronoUtil;
import com.intellij.codeInspection.util.OptionalUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
@@ -28,6 +29,7 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@@ -123,6 +125,14 @@ public final class CustomMethodHandlers {
toValue((args, memState, factory, method) -> OPTIONAL_VALUE.asDfType(memState.getDfType(args.myArguments[0]))))
.register(instanceCall(JAVA_UTIL_CALENDAR, "get").parameterTypes("int"),
toValue((args, memState, factory, method) -> calendarGet(args, memState, factory)))
.register(anyOf(instanceCall(JAVA_TIME_LOCAL_DATE, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
instanceCall(JAVA_TIME_LOCAL_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
instanceCall(JAVA_TIME_OFFSET_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
instanceCall(JAVA_TIME_ZONED_DATE_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField")
),
toValue((args, memState, factory, method) -> chronoGet(args, memState, method)))
.register(anyOf(instanceCall("java.io.InputStream", "skip").parameterTypes("long"),
instanceCall("java.io.Reader", "skip").parameterTypes("long")),
toValue((args, memState, factory, method) -> skip(args.myArguments, memState)))
@@ -393,6 +403,42 @@ public final class CustomMethodHandlers {
return isLong ? longRange(range.abs(LongRangeType.INT64)) : intRange(range.abs(LongRangeType.INT32));
}
private static @NotNull DfType chronoGet(DfaCallArguments arguments, DfaMemoryState state, PsiMethod method) {
DfaValue[] myArguments = arguments.myArguments;
if (myArguments.length != 1 || myArguments[0] == null) {
return DfType.TOP;
}
DfType myArgument = state.getDfType(myArguments[0]);
PsiEnumConstant enumConstant = myArgument.getConstantOfType(PsiEnumConstant.class);
if (enumConstant == null) {
return DfType.TOP;
}
PsiType enumType = enumConstant.getType();
if (!enumType.equalsToText("java.time.temporal.ChronoField")) {
return DfType.TOP;
}
PsiClass containingClass = method.getContainingClass();
if (containingClass == null) {
return DfType.TOP;
}
ChronoField myChronoField = ChronoUtil.getChronoField(enumConstant.getName());
if (myChronoField == null || !ChronoUtil.isGetSupported(method, myChronoField)) {
return DfType.TOP;
}
LongRangeSet range = LongRangeSet.range(myChronoField.range().getMinimum(), myChronoField.range().getMaximum());
String methodName = method.getName();
if ("get".equals(methodName)) {
return intRange(range);
}
else if ("getLong".equals(methodName)) {
return longRange(range);
}
else {
return DfType.TOP;
}
}
private static @NotNull DfType calendarGet(DfaCallArguments arguments, DfaMemoryState state, DfaValueFactory factory) {
if (arguments.myArguments.length != 1) return DfType.TOP;
Integer val = state.getDfType(arguments.myArguments[0]).getConstantOfType(Integer.class);

View File

@@ -0,0 +1,72 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.util;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.time.temporal.ChronoField;
import java.util.Arrays;
import java.util.Map;
import java.util.stream.Collectors;
public final class ChronoUtil {
private static final CallMatcher CHRONO_GET_MATCHERS = CallMatcher.anyOf(
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_LOCAL_DATE, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_LOCAL_DATE_TIME, "get", "getLong")
.parameterTypes("java.time.temporal.TemporalField"),
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_LOCAL_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_OFFSET_TIME, "get", "getLong").parameterTypes("java.time.temporal.TemporalField"),
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_OFFSET_DATE_TIME, "get", "getLong")
.parameterTypes("java.time.temporal.TemporalField"),
CallMatcher.instanceCall(CommonClassNames.JAVA_TIME_ZONED_DATE_TIME, "get", "getLong")
.parameterTypes("java.time.temporal.TemporalField")
);
private static final Map<String, ChronoField> chronoFieldMap = Arrays.stream(ChronoField.values())
.collect(Collectors.toMap(t -> t.name(), t -> t));
@Nullable
public static ChronoField getChronoField(@NotNull String name) {
return chronoFieldMap.get(name);
}
public static boolean isGetSupported(@NotNull PsiMethod method, @NotNull ChronoField chronoField) {
if (!CHRONO_GET_MATCHERS.methodMatches(method)) {
return false;
}
String methodName = method.getName();
if ("getInt".equals(methodName) && !chronoField.range().isIntValue()) {
return false;
}
PsiClass containingClass = method.getContainingClass();
if (containingClass == null) {
return false;
}
String classQualifiedName = containingClass.getQualifiedName();
if (chronoField.isDateBased() && (
CommonClassNames.JAVA_TIME_OFFSET_TIME.equals(classQualifiedName) ||
CommonClassNames.JAVA_TIME_LOCAL_TIME.equals(classQualifiedName))) {
return false;
}
if (chronoField.isTimeBased() && CommonClassNames.JAVA_TIME_LOCAL_DATE.equals(classQualifiedName)) {
return false;
}
if (chronoField == ChronoField.OFFSET_SECONDS &&
!(CommonClassNames.JAVA_TIME_OFFSET_TIME.equals(classQualifiedName) ||
CommonClassNames.JAVA_TIME_OFFSET_DATE_TIME.equals(classQualifiedName) ||
CommonClassNames.JAVA_TIME_ZONED_DATE_TIME.equals(classQualifiedName))) {
return false;
}
if (chronoField == ChronoField.INSTANT_SECONDS &&
!(CommonClassNames.JAVA_TIME_OFFSET_DATE_TIME.equals(classQualifiedName) ||
CommonClassNames.JAVA_TIME_ZONED_DATE_TIME.equals(classQualifiedName))) {
return false;
}
return true;
}
}

View File

@@ -0,0 +1,44 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.OffsetTime;
import java.time.temporal.ChronoField;
class Test {
private static void skipped(OffsetTime offsetTime, LocalDateTime localDateTime, LocalDate localDate, LocalTime localTime) {
if (offsetTime.get(ChronoField.INSTANT_SECONDS) == 0)
{
System.out.println("1");
}
if (localDateTime.get(ChronoField.OFFSET_SECONDS) > 1_000_000_000)
{
System.out.println("2");
}
if (localDate.get(ChronoField.MINUTE_OF_HOUR) > 1000)
{
System.out.println("3");
}
if (localTime.get(ChronoField.ERA) > 4)
{
System.out.println("4");
}
}
private static void checked(OffsetTime offsetTime, LocalDateTime localDateTime, LocalDate localDate, LocalTime localTime) {
if(<warning descr="Condition 'offsetTime.get(ChronoField.OFFSET_SECONDS)>1_000_000_000' is always 'false'">offsetTime.get(ChronoField.OFFSET_SECONDS)>1_000_000_000</warning>)
{
System.out.println("1");
}
if (<warning descr="Condition 'localDate.get(ChronoField.ERA) > 2' is always 'false'">localDate.get(ChronoField.ERA) > 2</warning>)
{
System.out.println("2");
}
if (<warning descr="Condition 'localTime.getLong(ChronoField.MINUTE_OF_HOUR) < 0' is always 'false'">localTime.getLong(ChronoField.MINUTE_OF_HOUR) < 0</warning>)
{
System.out.println("3");
}
if (<warning descr="Condition 'localDateTime.getLong(ChronoField.MINUTE_OF_HOUR) < 0' is always 'false'">localDateTime.getLong(ChronoField.MINUTE_OF_HOUR) < 0</warning>)
{
System.out.println("4");
}
}
}

View File

@@ -91,4 +91,44 @@ public class DataFlowRangeAnalysisTest extends DataFlowInspectionTestCase {
public void testStringIndexOfRelation() { doTest(); }
public void testIncompleteLoop() { doTest(); }
public void testTwoFlagsMixed() { doTest(); }
public void testChronoRange() {
myFixture.addClass("""
package java.time.temporal;
public interface TemporalField{}""");
myFixture.addClass("""
package java.time.temporal;
import java.time.temporal.TemporalField;
public enum ChronoField implements TemporalField{
INSTANT_SECONDS, OFFSET_SECONDS, MINUTE_OF_HOUR, ERA, EPOCH_DAY
}""");
myFixture.addClass("""
package java.time;
import java.time.temporal.TemporalField;
public interface OffsetTime{
int get(TemporalField field);
long getLong(TemporalField field);
}""");
myFixture.addClass("""
package java.time;
import java.time.temporal.TemporalField;
public interface LocalDateTime{
int get(TemporalField field);
long getLong(TemporalField field);
}""");
myFixture.addClass("""
package java.time;
import java.time.temporal.TemporalField;
public interface LocalDate{
int get(TemporalField field);
long getLong(TemporalField field);
}""");
myFixture.addClass("""
package java.time;
import java.time.temporal.TemporalField;
public interface LocalTime{
int get(TemporalField field);
long getLong(TemporalField field);
}""");
doTest();
}
}