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}