mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-07 22:09:38 +07:00
[java-intentions] IDEA-313717. Range for get and getLong methods of java.time TemporalAccessor
GitOrigin-RevId: c5f5b3ce075b1da349eef7695e955107bb50626c
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2f754aadf4
commit
ed61db9030
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user