package org.checkerframework.common.value; import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import org.checkerframework.common.basetype.BaseTypeChecker; import org.checkerframework.framework.source.Result; import org.checkerframework.framework.util.PluginUtil; import org.checkerframework.javacutil.ElementUtils; import org.checkerframework.javacutil.TreeUtils; import org.checkerframework.javacutil.TypesUtils; public class ReflectiveEvaluator { private BaseTypeChecker checker; private boolean reportWarnings; public ReflectiveEvaluator( BaseTypeChecker checker, ValueAnnotatedTypeFactory factory, boolean reportWarnings) { this.checker = checker; this.reportWarnings = reportWarnings; } /** * @param allArgValues a list of list where the first list corresponds to all possible values * for the first argument. Pass null to indicate that the method has no arguments. * @param receiverValues a list of possible receiver values. null indicates that the method has * no receiver. * @param tree location to report any errors * @return all possible values that the method may return, or null if the method could not be * evaluated */ public List<?> evaluateMethodCall( List<List<?>> allArgValues, List<?> receiverValues, MethodInvocationTree tree) { Method method = getMethodObject(tree); if (method == null) { return null; } if (receiverValues == null) { // Method does not have a receiver // the first parameter of Method.invoke should be null receiverValues = Collections.singletonList(null); } List<Object[]> listOfArguments; if (allArgValues == null) { // Method does not have arguments listOfArguments = new ArrayList<Object[]>(); listOfArguments.add(null); } else { // Find all possible argument sets listOfArguments = cartesianProduct(allArgValues, allArgValues.size() - 1); } if (method.isVarArgs()) { List<Object[]> newList = new ArrayList<>(); int numberOfParameters = method.getParameterTypes().length; for (Object[] args : listOfArguments) { newList.add(normalizeVararg(args, numberOfParameters)); } listOfArguments = newList; } List<Object> results = new ArrayList<>(); for (Object[] arguments : listOfArguments) { for (Object receiver : receiverValues) { try { results.add(method.invoke(receiver, arguments)); } catch (InvocationTargetException e) { if (reportWarnings) { checker.report( Result.warning( "method.evaluation.exception", method, e.getTargetException().toString()), tree); } // Method evaluation will always fail, so don't bother // trying again return null; } catch (ExceptionInInitializerError e) { if (reportWarnings) { checker.report( Result.warning( "method.evaluation.exception", method, e.getCause().toString()), tree); } return null; } catch (IllegalArgumentException e) { if (reportWarnings) { String args = PluginUtil.join(", ", arguments); checker.report( Result.warning( "method.evaluation.exception", method, e.getLocalizedMessage() + ": " + args), tree); } return null; } catch (Throwable e) { // Catch any exception thrown because they shouldn't crash the type checker. if (reportWarnings) { checker.report(Result.warning("method.evaluation.failed", method), tree); } return null; } } } return results; } /** * This method normalizes an array of arguments to a varargs method by changing the arguments * associated with the varargs parameter into an array. * * @param arguments an array of arguments for {@code method}. The length is at least {@code * numberOfParameters - 1}. * @param numberOfParameters number of parameters of the vararg method * @return the length of the array is exactly {@code numberOfParameters} */ private Object[] normalizeVararg(Object[] arguments, int numberOfParameters) { if (arguments == null) { // null means no arguments. For varargs no arguments is an empty array. arguments = new Object[] {}; } Object[] newArgs = new Object[numberOfParameters]; Object[] varArgsArray; int numOfVarArgs = arguments.length - numberOfParameters + 1; if (numOfVarArgs > 0) { System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); varArgsArray = new Object[numOfVarArgs]; System.arraycopy(arguments, numberOfParameters - 1, varArgsArray, 0, numOfVarArgs); } else { System.arraycopy(arguments, 0, newArgs, 0, numberOfParameters - 1); varArgsArray = new Object[] {}; } newArgs[numberOfParameters - 1] = varArgsArray; return newArgs; } /** * Method for reflectively obtaining a method object so it can (potentially) be statically * executed by the checker for constant propagation * * @return the Method object corresponding to the method being invoke in tree */ private Method getMethodObject(MethodInvocationTree tree) { try { ExecutableElement ele = TreeUtils.elementFromUse(tree); Name clazz = TypesUtils.getQualifiedName((DeclaredType) ele.getEnclosingElement().asType()); List<Class<?>> paramClzz = getParameterClasses(tree, ele); Class<?> clzz = Class.forName(clazz.toString()); Method method = clzz.getMethod( ele.getSimpleName().toString(), paramClzz.toArray(new Class<?>[0])); if (!method.isAccessible()) { method.setAccessible(true); } return method; } catch (ClassNotFoundException | UnsupportedClassVersionError e) { if (reportWarnings) { checker.report( Result.warning( "class.find.failed", (TreeUtils.elementFromUse(tree)).getEnclosingElement()), tree); } return null; } catch (NoSuchMethodException e) { // The class we attempted to getMethod from inside the // call to getMethodObject. Element classElem = TreeUtils.elementFromUse(tree).getEnclosingElement(); if (classElem == null) { if (reportWarnings) { checker.report(Result.warning("method.find.failed"), tree); } } else { if (reportWarnings) { checker.report(Result.warning("method.find.failed.in.class", classElem), tree); } } return null; } } private List<Class<?>> getParameterClasses(Tree tree, ExecutableElement ele) throws ClassNotFoundException { List<? extends VariableElement> paramEles = ele.getParameters(); List<Class<?>> paramClzz = new ArrayList<>(); for (Element e : paramEles) { TypeMirror pType = ElementUtils.getType(e); paramClzz.add(ValueCheckerUtils.getClassFromType(pType)); } return paramClzz; } private List<Object[]> cartesianProduct(List<List<?>> allArgValues, int whichArg) { List<?> argValues = allArgValues.get(whichArg); List<Object[]> tuples = new ArrayList<>(); for (Object value : argValues) { if (whichArg == 0) { Object[] objects = new Object[allArgValues.size()]; objects[0] = value; tuples.add(objects); } else { List<Object[]> lastTuples = cartesianProduct(allArgValues, whichArg - 1); List<Object[]> copies = copy(lastTuples); for (Object[] copy : copies) { copy[whichArg] = value; } tuples.addAll(copies); } } return tuples; } private List<Object[]> copy(List<Object[]> lastTuples) { List<Object[]> returnListOfLists = new ArrayList<>(); for (Object[] list : lastTuples) { Object[] copy = Arrays.copyOf(list, list.length); returnListOfLists.add(copy); } return returnListOfLists; } public Object evaluateStaticFieldAccess( String classname, String fieldName, MemberSelectTree tree) { try { Class<?> recClass = Class.forName(classname); Field field = recClass.getField(fieldName.toString()); return field.get(recClass); } catch (ClassNotFoundException | UnsupportedClassVersionError e) { if (reportWarnings) { checker.report(Result.warning("class.find.failed", classname), tree); } return null; } catch (ReflectiveOperationException e) { if (reportWarnings) { checker.report(Result.warning("field.access.failed", fieldName, classname), tree); } return null; } } public List<?> evaluteConstructorCall( ArrayList<List<?>> argValues, NewClassTree tree, TypeMirror typeToCreate) { try { // get the constructor Constructor<?> constructor = getConstructorObject(tree, typeToCreate); if (constructor == null) { return null; } List<Object[]> listOfArguments; if (argValues == null) { // Method does not have arguments listOfArguments = new ArrayList<Object[]>(); listOfArguments.add(null); } else { // Find all possible argument sets listOfArguments = cartesianProduct(argValues, argValues.size() - 1); } List<Object> results = new ArrayList<>(); for (Object[] arguments : listOfArguments) { try { results.add(constructor.newInstance(arguments)); } catch (ReflectiveOperationException e) { if (reportWarnings) { checker.report(Result.warning("constructor.invocation.failed"), tree); } return null; } } return results; } catch (ReflectiveOperationException e) { if (reportWarnings) { checker.report(Result.warning("constructor.evaluation.failed"), tree); } return null; } } private Constructor<?> getConstructorObject(NewClassTree tree, TypeMirror typeToCreate) throws ClassNotFoundException, NoSuchMethodException { ExecutableElement ele = TreeUtils.elementFromUse(tree); List<Class<?>> paramClasses = getParameterClasses(tree, ele); Class<?> recClass = boxPrimitives(ValueCheckerUtils.getClassFromType(typeToCreate)); Constructor<?> constructor = recClass.getConstructor(paramClasses.toArray(new Class<?>[0])); return constructor; } /** * Returns the box primitive type if the passed type is an (unboxed) primitive. Otherwise it * returns the passed type */ private static Class<?> boxPrimitives(Class<?> type) { if (type == byte.class) { return Byte.class; } else if (type == short.class) { return Short.class; } else if (type == int.class) { return Integer.class; } else if (type == long.class) { return Long.class; } else if (type == float.class) { return Float.class; } else if (type == double.class) { return Double.class; } else if (type == char.class) { return Character.class; } else if (type == boolean.class) { return Boolean.class; } return type; } }