constructor reference: don't ignore constructor parameters during method reference...
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / dataFlow / CustomMethodHandlers.java
1 // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInspection.dataFlow;
3
4 import com.intellij.codeInspection.dataFlow.rangeSet.LongRangeSet;
5 import com.intellij.codeInspection.dataFlow.value.DfaConstValue;
6 import com.intellij.codeInspection.dataFlow.value.DfaValue;
7 import com.intellij.codeInspection.dataFlow.value.DfaValueFactory;
8 import com.intellij.codeInspection.util.OptionalUtil;
9 import com.intellij.psi.*;
10 import com.intellij.psi.util.CachedValueProvider;
11 import com.intellij.psi.util.CachedValuesManager;
12 import com.intellij.util.ArrayUtil;
13 import com.intellij.util.ReflectionUtil;
14 import com.siyeh.ig.callMatcher.CallMapper;
15 import com.siyeh.ig.callMatcher.CallMatcher;
16 import com.siyeh.ig.psiutils.TypeUtils;
17 import org.jetbrains.annotations.Contract;
18 import org.jetbrains.annotations.NotNull;
19 import org.jetbrains.annotations.Nullable;
20
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.util.ArrayList;
24 import java.util.Calendar;
25 import java.util.List;
26
27 import static com.intellij.psi.CommonClassNames.*;
28 import static com.siyeh.ig.callMatcher.CallMatcher.*;
29
30 class CustomMethodHandlers {
31   private static final CallMatcher CONSTANT_CALLS = anyOf(
32     exactInstanceCall(JAVA_LANG_STRING, "contains", "indexOf", "startsWith", "endsWith", "lastIndexOf", "length", "trim",
33                  "substring", "equals", "equalsIgnoreCase", "charAt", "codePointAt", "compareTo", "replace"),
34     staticCall(JAVA_LANG_STRING, "valueOf").parameterCount(1),
35     staticCall(JAVA_LANG_MATH, "abs", "sqrt", "min", "max"),
36     staticCall(JAVA_LANG_INTEGER, "toString", "toBinaryString", "toHexString", "toOctalString", "toUnsignedString").parameterTypes("int"),
37     staticCall(JAVA_LANG_LONG, "toString", "toBinaryString", "toHexString", "toOctalString", "toUnsignedString").parameterTypes("long"),
38     staticCall(JAVA_LANG_DOUBLE, "toString", "toHexString").parameterTypes("double"),
39     staticCall(JAVA_LANG_FLOAT, "toString", "toHexString").parameterTypes("float"),
40     staticCall(JAVA_LANG_BYTE, "toString").parameterTypes("byte"),
41     staticCall(JAVA_LANG_SHORT, "toString").parameterTypes("short")
42   );
43   private static final int MAX_STRING_CONSTANT_LENGTH_TO_TRACK = 1024;
44
45   interface CustomMethodHandler {
46
47     @Nullable
48     DfaValue getMethodResult(DfaCallArguments callArguments, DfaMemoryState memState, DfaValueFactory factory);
49
50     default CustomMethodHandler compose(CustomMethodHandler other) {
51       if (other == null) return this;
52       return (args, memState, factory) -> {
53         DfaValue result = this.getMethodResult(args, memState, factory);
54         return result == null ? other.getMethodResult(args, memState, factory) : result;
55       };
56     }
57
58   }
59   private static final CallMapper<CustomMethodHandler> CUSTOM_METHOD_HANDLERS = new CallMapper<CustomMethodHandler>()
60     .register(instanceCall(JAVA_LANG_STRING, "indexOf", "lastIndexOf"),
61               (args, memState, factory) -> indexOf(args.myQualifier, memState, factory, SpecialField.STRING_LENGTH))
62     .register(instanceCall(JAVA_UTIL_LIST, "indexOf", "lastIndexOf"),
63               (args, memState, factory) -> indexOf(args.myQualifier, memState, factory, SpecialField.COLLECTION_SIZE))
64     .register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("int"),
65               (args, memState, factory) -> mathAbs(args.myArguments, memState, factory, false))
66     .register(staticCall(JAVA_LANG_MATH, "abs").parameterTypes("long"),
67               (args, memState, factory) -> mathAbs(args.myArguments, memState, factory, true))
68     .register(OptionalUtil.OPTIONAL_OF_NULLABLE,
69               (args, memState, factory) -> ofNullable(args.myArguments[0], memState, factory))
70     .register(instanceCall(JAVA_UTIL_CALENDAR, "get").parameterTypes("int"),
71               (args, memState, factory) -> calendarGet(args.myArguments, memState, factory))
72     .register(anyOf(instanceCall("java.io.InputStream", "skip").parameterTypes("long"),
73                     instanceCall("java.io.Reader", "skip").parameterTypes("long")),
74               (args, memState, factory) -> skip(args.myArguments, memState, factory))
75     .register(staticCall(JAVA_LANG_INTEGER, "toHexString").parameterCount(1),
76               (args, memState, factory) -> numberAsString(args, memState, factory, 4, Integer.SIZE))
77     .register(staticCall(JAVA_LANG_INTEGER, "toOctalString").parameterCount(1),
78               (args, memState, factory) -> numberAsString(args, memState, factory, 3, Integer.SIZE))
79     .register(staticCall(JAVA_LANG_INTEGER, "toBinaryString").parameterCount(1),
80               (args, memState, factory) -> numberAsString(args, memState, factory, 1, Integer.SIZE))
81     .register(staticCall(JAVA_LANG_LONG, "toHexString").parameterCount(1),
82               (args, memState, factory) -> numberAsString(args, memState, factory, 4, Long.SIZE))
83     .register(staticCall(JAVA_LANG_LONG, "toOctalString").parameterCount(1),
84               (args, memState, factory) -> numberAsString(args, memState, factory, 3, Long.SIZE))
85     .register(staticCall(JAVA_LANG_LONG, "toBinaryString").parameterCount(1),
86               (args, memState, factory) -> numberAsString(args, memState, factory, 1, Long.SIZE));
87
88   public static CustomMethodHandler find(PsiMethod method) {
89     CustomMethodHandler handler = null;
90     if (isConstantCall(method)) {
91       handler = (args, memState, factory) -> handleConstantCall(args, memState, factory, method);
92     }
93     CustomMethodHandler handler2 = CUSTOM_METHOD_HANDLERS.mapFirst(method);
94     return handler == null ? handler2 : handler.compose(handler2);
95   }
96
97   @Contract("null -> false")
98   private static boolean isConstantCall(PsiMethod method) {
99     return CONSTANT_CALLS.methodMatches(method);
100   }
101
102   @Nullable
103   private static DfaValue handleConstantCall(DfaCallArguments arguments, DfaMemoryState state,
104                                              DfaValueFactory factory, PsiMethod method) {
105     PsiType returnType = method.getReturnType();
106     if (returnType == null) return null;
107     List<Object> args = new ArrayList<>();
108     Object qualifierValue = null;
109     if (!method.hasModifierProperty(PsiModifier.STATIC)) {
110       qualifierValue = getConstantValue(state, arguments.myQualifier);
111       if (qualifierValue == null) return null;
112     }
113     for (DfaValue argument : arguments.myArguments) {
114       Object argumentValue = getConstantValue(state, argument);
115       if (argumentValue == null) return null;
116       if (argumentValue instanceof Long) {
117         long longValue = ((Long)argumentValue).longValue();
118         if (longValue >= Integer.MIN_VALUE && longValue <= Integer.MAX_VALUE) {
119           argumentValue = (int)longValue;
120         }
121       }
122       args.add(argumentValue);
123     }
124     Method jvmMethod = toJvmMethod(method);
125     if (jvmMethod == null) return null;
126     Object result;
127     try {
128       result = jvmMethod.invoke(qualifierValue, args.toArray());
129     }
130     catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
131       return null;
132     }
133     return factory.getConstFactory().createFromValue(result, returnType);
134   }
135
136   private static Method toJvmMethod(PsiMethod method) {
137     return CachedValuesManager.getCachedValue(method, new CachedValueProvider<Method>() {
138       @NotNull
139       @Override
140       public Result<Method> compute() {
141         Method reflection = getMethod();
142         return Result.create(reflection, method);
143       }
144
145       private Class<?> toJvmType(PsiType type) {
146         if (TypeUtils.isJavaLangString(type)) {
147           return String.class;
148         }
149         if (TypeUtils.isJavaLangObject(type)) {
150           return Object.class;
151         }
152         if (TypeUtils.typeEquals(JAVA_LANG_CHAR_SEQUENCE, type)) {
153           return CharSequence.class;
154         }
155         if (PsiType.INT.equals(type)) {
156           return int.class;
157         }
158         if (PsiType.BOOLEAN.equals(type)) {
159           return boolean.class;
160         }
161         if (PsiType.CHAR.equals(type)) {
162           return char.class;
163         }
164         if (PsiType.LONG.equals(type)) {
165           return long.class;
166         }
167         if (PsiType.FLOAT.equals(type)) {
168           return float.class;
169         }
170         if (PsiType.DOUBLE.equals(type)) {
171           return double.class;
172         }
173         return null;
174       }
175
176       @Nullable
177       private Method getMethod() {
178         PsiClass aClass = method.getContainingClass();
179         Class<?> containingClass;
180         if (aClass == null) return null;
181         try {
182           containingClass = Class.forName(aClass.getQualifiedName());
183         }
184         catch (ClassNotFoundException ignored) {
185           return null;
186         }
187         PsiParameter[] parameters = method.getParameterList().getParameters();
188         Class<?>[] parameterTypes = new Class[parameters.length];
189         for (int i = 0; i < parameters.length; i++) {
190           PsiParameter parameter = parameters[i];
191           PsiType type = parameter.getType();
192           Class<?> jvmType = toJvmType(type);
193           if (jvmType == null) return null;
194           parameterTypes[i] = jvmType;
195         }
196         return ReflectionUtil.getMethod(containingClass, method.getName(), parameterTypes);
197       }
198     });
199   }
200
201   private static DfaValue indexOf(DfaValue qualifier,
202                                   DfaMemoryState memState,
203                                   DfaValueFactory factory,
204                                   SpecialField specialField) {
205     DfaValue length = specialField.createValue(factory, qualifier);
206     LongRangeSet range = memState.getValueFact(length, DfaFactType.RANGE);
207     long maxLen = range == null || range.isEmpty() ? Integer.MAX_VALUE : range.max();
208     return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(-1, maxLen - 1));
209   }
210
211   private static DfaValue ofNullable(DfaValue argument, DfaMemoryState state, DfaValueFactory factory) {
212     if (state.isNull(argument)) {
213       return DfaOptionalSupport.getOptionalValue(factory, false);
214     }
215     if (state.isNotNull(argument)) {
216       return DfaOptionalSupport.getOptionalValue(factory, true);
217     }
218     return null;
219   }
220
221   private static DfaValue mathAbs(DfaValue[] args, DfaMemoryState memState, DfaValueFactory factory, boolean isLong) {
222     DfaValue arg = ArrayUtil.getFirstElement(args);
223     if (arg == null) return null;
224     LongRangeSet range = memState.getValueFact(arg, DfaFactType.RANGE);
225     if (range == null) return null;
226     return factory.getFactValue(DfaFactType.RANGE, range.abs(isLong));
227   }
228
229   private static DfaValue calendarGet(DfaValue[] arguments, DfaMemoryState state, DfaValueFactory factory) {
230     if (arguments.length != 1) return null;
231     DfaConstValue arg = state.getConstantValue(arguments[0]);
232     if (arg == null || !(arg.getValue() instanceof Long)) return null;
233     LongRangeSet range = null;
234     switch (((Long)arg.getValue()).intValue()) {
235       case Calendar.DATE: range = LongRangeSet.range(1, 31); break;
236       case Calendar.MONTH: range = LongRangeSet.range(0, 12); break;
237       case Calendar.AM_PM: range = LongRangeSet.range(0, 1); break;
238       case Calendar.DAY_OF_YEAR: range = LongRangeSet.range(1, 366); break;
239       case Calendar.HOUR: range = LongRangeSet.range(0, 11); break;
240       case Calendar.HOUR_OF_DAY: range = LongRangeSet.range(0, 23); break;
241       case Calendar.MINUTE:
242       case Calendar.SECOND: range = LongRangeSet.range(0, 59); break;
243       case Calendar.MILLISECOND: range = LongRangeSet.range(0, 999); break;
244     }
245     return range == null ? null : factory.getFactValue(DfaFactType.RANGE, range);
246   }
247
248   private static DfaValue skip(DfaValue[] arguments, DfaMemoryState state, DfaValueFactory factory) {
249     if (arguments.length != 1) return null;
250     LongRangeSet range = state.getValueFact(arguments[0], DfaFactType.RANGE);
251     if (range == null || range.isEmpty()) return null;
252     return factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(0, Math.max(0, range.max())));
253   }
254
255
256   private static DfaValue numberAsString(DfaCallArguments args, DfaMemoryState state, DfaValueFactory factory, int bitsPerChar,
257                                          int maxBits) {
258     DfaValue arg = args.myArguments[0];
259     if (arg == null) return null;
260     LongRangeSet range = state.getValueFact(arg, DfaFactType.RANGE);
261     if (range == null || range.isEmpty()) return null;
262     int usedBits = range.min() >= 0 ? Long.SIZE - Long.numberOfLeadingZeros(range.max()) : maxBits;
263     int max = Math.max(1, (usedBits - 1) / bitsPerChar + 1);
264     DfaValue lengthRange = factory.getFactValue(DfaFactType.RANGE, LongRangeSet.range(1, max));
265     DfaFactMap map = DfaFactMap.EMPTY.with(DfaFactType.NULLABILITY, DfaNullability.NOT_NULL)
266       .with(DfaFactType.SPECIAL_FIELD_VALUE, SpecialField.STRING_LENGTH.withValue(lengthRange));
267     return factory.getFactFactory().createValue(map);
268   }
269
270   private static Object getConstantValue(DfaMemoryState memoryState, DfaValue value) {
271     if (value != null) {
272       LongRangeSet fact = memoryState.getValueFact(value, DfaFactType.RANGE);
273       Long constantValue = fact == null ? null : fact.getConstantValue();
274       if (constantValue != null) {
275         return constantValue;
276       }
277     }
278     DfaConstValue dfaConst = memoryState.getConstantValue(value);
279     if (dfaConst != null) {
280       Object constant = dfaConst.getValue();
281       if (constant instanceof String && ((String)constant).length() > MAX_STRING_CONSTANT_LENGTH_TO_TRACK) return null;
282       return constant;
283     }
284     return null;
285   }
286 }