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}