001package daikon.chicory; 002 003import java.lang.reflect.InvocationTargetException; 004import java.lang.reflect.Method; 005import java.util.ArrayList; 006import java.util.List; 007import org.checkerframework.checker.initialization.qual.Initialized; 008import org.checkerframework.checker.lock.qual.GuardedBy; 009import org.checkerframework.checker.nullness.qual.NonNull; 010import org.checkerframework.checker.nullness.qual.Nullable; 011 012/** 013 * The PureMethodInfo class is a subtype of DaikonVariableInfo used for "variable types" which 014 * correspond to the values of pure method invocations. 015 */ 016public class PureMethodInfo extends DaikonVariableInfo { 017 018 /** The MethodInfo object for this pure method. */ 019 private MethodInfo minfo; 020 021 /** An array containing the parameters of this pure method. */ 022 private DaikonVariableInfo[] args; 023 024 public PureMethodInfo( 025 String name, 026 MethodInfo methInfo, 027 String typeName, 028 String repTypeName, 029 String receiverName, 030 boolean inArray) { 031 this(name, methInfo, typeName, repTypeName, receiverName, inArray, new DaikonVariableInfo[0]); 032 } 033 034 public PureMethodInfo( 035 String name, 036 MethodInfo methInfo, 037 String typeName, 038 String repTypeName, 039 String receiverName, 040 boolean inArray, 041 DaikonVariableInfo[] args) { 042 super(name, typeName, repTypeName, inArray); 043 044 assert methInfo.isPure() : "Method " + methInfo + " is not pure"; 045 046 minfo = methInfo; 047 048 this.args = args; 049 050 // Update function_args 051 function_args = receiverName; 052 if (this.args.length != 0) { 053 for (int i = 0; i < args.length; i++) { 054 function_args += " " + args[i].getName(); 055 } 056 } 057 } 058 059 /** Invokes this pure method on the given parentVal. This is safe because the method is pure! */ 060 @Override 061 @SuppressWarnings({ 062 "unchecked", 063 "deprecation" // in Java 9+, use canAccess instead of isAccessible 064 }) 065 public @Nullable Object getMyValFromParentVal(Object parentVal) { 066 @SuppressWarnings("nullness") // not a class initializer, so meth != null 067 @NonNull Method meth = (Method) minfo.member; 068 boolean changedAccess = false; 069 Object retVal; 070 071 // we want to access all methods... 072 if (!meth.isAccessible()) { 073 changedAccess = true; 074 meth.setAccessible(true); 075 } 076 077 if (isArray) { 078 // First check if parentVal is null or nonsensical 079 if (parentVal == null || parentVal instanceof NonsensicalList) { 080 retVal = NonsensicalList.getInstance(); 081 } else { 082 ArrayList<@Nullable Object> retList = new ArrayList<>(); 083 084 for (Object val : (List<Object>) parentVal) { // unchecked cast 085 if (val == null || val instanceof NonsensicalObject) { 086 retList.add(NonsensicalObject.getInstance()); 087 } else { 088 retList.add(executePureMethod(meth, val, getArgVals(parentVal))); 089 } 090 } 091 092 retVal = retList; 093 } 094 } else { 095 // First check if parentVal is null or nonsensical 096 if (parentVal == null || parentVal instanceof NonsensicalObject) { 097 retVal = NonsensicalObject.getInstance(); 098 } else { 099 retVal = executePureMethod(meth, parentVal, getArgVals(parentVal)); 100 } 101 } 102 103 if (changedAccess) { 104 meth.setAccessible(false); 105 } 106 107 return retVal; 108 } 109 110 /** 111 * Returns the current values of this pure method's arguments based on {@code parentVal}. 112 * 113 * @param parentVal the parent of the current method 114 * @return an Object the values of this method's arguments 115 */ 116 private @Nullable Object[] getArgVals(Object parentVal) { 117 @Nullable Object[] params = new @Nullable Object[args.length]; 118 119 for (int i = 0; i < args.length; i++) { 120 Object currentVal = args[i].getMyValFromParentVal(parentVal); 121 122 if (currentVal instanceof Runtime.PrimitiveWrapper) { 123 // Convert Chicory primitive wrapper to java.lang's primitive wrapper 124 Runtime.PrimitiveWrapper x = (Runtime.PrimitiveWrapper) currentVal; 125 params[i] = x.getJavaWrapper(); 126 } else { 127 params[i] = currentVal; 128 } 129 } 130 return params; 131 } 132 133 /** 134 * Returns the result of invoking the method. 135 * 136 * @param meth a method 137 * @param receiverVal the receiver value 138 * @param argVals the argument values 139 * @return the result of invoking the method 140 */ 141 @SuppressWarnings("LockOnNonEnclosingClassLiteral") // `synchronize on Runtime.class` 142 private static @Nullable Object executePureMethod( 143 Method meth, Object receiverVal, @Nullable Object[] argVals) { 144 // Between startPure() and endPure(), no output is done to the trace file. 145 // Without this synchronization, other threads would observe that 146 // startPure has been called and wouldn't do any output. 147 synchronized (Runtime.class) { 148 Object retVal; 149 try { 150 // TODO is this the best way to handle this problem? 151 // (when we invoke a pure method, Runtime.Enter should not be 152 // called) 153 Runtime.startPure(); 154 155 @SuppressWarnings("nullness") // argVals is declared Nullable 156 @NonNull @Initialized @GuardedBy({}) Object tmp_retVal = meth.invoke(receiverVal, argVals); 157 retVal = tmp_retVal; 158 159 if (meth.getReturnType().isPrimitive()) { 160 retVal = convertWrapper(retVal); 161 } 162 163 } catch (IllegalArgumentException e) { 164 throw new Error(e); 165 } catch (IllegalAccessException e) { 166 throw new Error(e); 167 } catch (InvocationTargetException e) { 168 retVal = NonsensicalObject.getInstance(); 169 } catch (Throwable e) { 170 throw new Error(e); 171 } finally { 172 Runtime.endPure(); 173 } 174 175 return retVal; 176 } 177 } 178 179 /** 180 * Convert standard wrapped (boxed) Objects (i.e., Integers) to Chicory wrappers (ie, 181 * Runtime.IntWrap). Should not be called if the Object was not auto-boxed from from a primitive! 182 */ 183 public static @Nullable Object convertWrapper(@Nullable Object obj) { 184 if (obj == null || obj instanceof NonsensicalObject || obj instanceof NonsensicalList) { 185 return obj; 186 } 187 188 if (obj instanceof Integer) { 189 return new Runtime.IntWrap((Integer) obj); 190 } else if (obj instanceof Boolean) { 191 return new Runtime.BooleanWrap((Boolean) obj); 192 } else if (obj instanceof Byte) { 193 return new Runtime.ByteWrap((Byte) obj); 194 } else if (obj instanceof Character) { 195 return new Runtime.CharWrap((Character) obj); 196 } else if (obj instanceof Float) { 197 return new Runtime.FloatWrap((Float) obj); 198 } else if (obj instanceof Double) { 199 return new Runtime.DoubleWrap((Double) obj); 200 } else if (obj instanceof Long) { 201 return new Runtime.LongWrap((Long) obj); 202 } else if (obj instanceof Short) { 203 return new Runtime.ShortWrap((Short) obj); 204 } else { 205 // Not a primitive object (wrapper), so just keep it the same 206 return obj; 207 } 208 } 209 210 @Override 211 public VarKind get_var_kind() { 212 return VarKind.FUNCTION; 213 } 214 215 /** Return the short name of the method as the relative name. */ 216 @Override 217 public String get_relative_name() { 218 return minfo.method_name; 219 } 220}