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 an array corresponding to the current values of this pure method's arguments based on 112 * the given parentVal. 113 */ 114 private @Nullable Object[] getArgVals(Object parentVal) { 115 @Nullable Object[] params = new @Nullable Object[args.length]; 116 117 for (int i = 0; i < args.length; i++) { 118 Object currentVal = args[i].getMyValFromParentVal(parentVal); 119 120 if (currentVal instanceof Runtime.PrimitiveWrapper) { 121 // Convert Chicory primitive wrapper to java.lang's primitive wrapper 122 Runtime.PrimitiveWrapper x = (Runtime.PrimitiveWrapper) currentVal; 123 params[i] = x.getJavaWrapper(); 124 } else { 125 params[i] = currentVal; 126 } 127 } 128 return params; 129 } 130 131 private static @Nullable Object executePureMethod( 132 Method meth, Object receiverVal, @Nullable Object[] argVals) { 133 // Between startPure() and endPure(), no output is done to the trace file. 134 // Without this synchronization, other threads would observe that 135 // startPure has been called and wouldn't do any output. 136 synchronized (Runtime.class) { 137 Object retVal; 138 try { 139 // TODO is this the best way to handle this problem? 140 // (when we invoke a pure method, Runtime.Enter should not be 141 // called) 142 Runtime.startPure(); 143 144 @SuppressWarnings("nullness") // argVals is declared Nullable 145 @NonNull @Initialized @GuardedBy({}) Object tmp_retVal = meth.invoke(receiverVal, argVals); 146 retVal = tmp_retVal; 147 148 if (meth.getReturnType().isPrimitive()) { 149 retVal = convertWrapper(retVal); 150 } 151 152 } catch (IllegalArgumentException e) { 153 throw new Error(e); 154 } catch (IllegalAccessException e) { 155 throw new Error(e); 156 } catch (InvocationTargetException e) { 157 retVal = NonsensicalObject.getInstance(); 158 } catch (Throwable e) { 159 throw new Error(e); 160 } finally { 161 Runtime.endPure(); 162 } 163 164 return retVal; 165 } 166 } 167 168 /** 169 * Convert standard wrapped (boxed) Objects (i.e., Integers) to Chicory wrappers (ie, 170 * Runtime.IntWrap). Should not be called if the Object was not auto-boxed from from a primitive! 171 */ 172 public static @Nullable Object convertWrapper(@Nullable Object obj) { 173 if (obj == null || obj instanceof NonsensicalObject || obj instanceof NonsensicalList) { 174 return obj; 175 } 176 177 if (obj instanceof Integer) { 178 return new Runtime.IntWrap((Integer) obj); 179 } else if (obj instanceof Boolean) { 180 return new Runtime.BooleanWrap((Boolean) obj); 181 } else if (obj instanceof Byte) { 182 return new Runtime.ByteWrap((Byte) obj); 183 } else if (obj instanceof Character) { 184 return new Runtime.CharWrap((Character) obj); 185 } else if (obj instanceof Float) { 186 return new Runtime.FloatWrap((Float) obj); 187 } else if (obj instanceof Double) { 188 return new Runtime.DoubleWrap((Double) obj); 189 } else if (obj instanceof Long) { 190 return new Runtime.LongWrap((Long) obj); 191 } else if (obj instanceof Short) { 192 return new Runtime.ShortWrap((Short) obj); 193 } else { 194 // Not a primitive object (wrapper), so just keep it the same 195 return obj; 196 } 197 } 198 199 @Override 200 public VarKind get_var_kind() { 201 return VarKind.FUNCTION; 202 } 203 204 /** Return the short name of the method as the relative name. */ 205 @Override 206 public String get_relative_name() { 207 return minfo.method_name; 208 } 209}