All Downloads are FREE. Search and download functionalities are using the official Maven repository.

soot.LambdaMetaFactory Maven / Gradle / Ivy

package soot;

/*-
 * #%L
 * Soot - a J*va Optimization Framework
 * %%
 * Copyright (C) 2017 Brian Alliet Initial implementation
 * Copyright (C) 2018 Manuel Benz Bug fixes and improvements
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 2.1 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU General Lesser Public
 * License along with this program.  If not, see
 * .
 * #L%
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import soot.asm.AsmUtil;
import soot.javaToJimple.LocalGenerator;
import soot.jimple.ClassConstant;
import soot.jimple.IntConstant;
import soot.jimple.InvokeExpr;
import soot.jimple.InvokeStmt;
import soot.jimple.Jimple;
import soot.jimple.JimpleBody;
import soot.jimple.MethodHandle;
import soot.jimple.MethodType;
import soot.jimple.NewExpr;
import soot.jimple.ParameterRef;
import soot.jimple.SpecialInvokeExpr;
import soot.jimple.toolkits.scalar.LocalNameStandardizer;
import soot.util.Chain;
import soot.util.HashChain;

public final class LambdaMetaFactory {
  private static final Logger LOGGER = LoggerFactory.getLogger(LambdaMetaFactory.class);

  private final Wrapper wrapper;

  private int uniq;

  public LambdaMetaFactory(Singletons.Global g) {
    uniq = 0;
    wrapper = new Wrapper();
  }

  public static LambdaMetaFactory v() {
    return G.v().soot_LambdaMetaFactory();
  }

  /**
   * 
   * @param bootstrapArgs
   * @param tag
   * @param name
   * @param invokedType
   *          types of captured arguments, the last element is always the type of the FunctionalInterface
   * @param name
   * @return
   */
  // FIXME: synchronized to work around concurrency errors; possibly covering up actual problems
  public synchronized SootMethodRef makeLambdaHelper(List bootstrapArgs, int tag, String name,
      Type[] invokedType, SootClass enclosingClass) {
    if (bootstrapArgs.size() < 3 || !(bootstrapArgs.get(0) instanceof MethodType)
        || !(bootstrapArgs.get(1) instanceof MethodHandle) || !(bootstrapArgs.get(2) instanceof MethodType)
        || (bootstrapArgs.size() > 3 && !(bootstrapArgs.get(3) instanceof IntConstant))) {
      LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.metaFactory: {}", bootstrapArgs);
      return null;
    }
    /** implemented method type */
    MethodType samMethodType = ((MethodType) bootstrapArgs.get(0));

    /** the MethodHandle providing the implementation */
    MethodHandle implMethod = ((MethodHandle) bootstrapArgs.get(1));

    /** allows restrictions on invocation */
    MethodType instantiatedMethodType = ((MethodType) bootstrapArgs.get(2));

    int flags = 0;
    if (bootstrapArgs.size() > 3) {
      flags = ((IntConstant) bootstrapArgs.get(3)).value;
    }

    boolean serializable = (flags & 1 /* FLAGS_SERIALIZABLE */) != 0;
    List markerInterfaces = new ArrayList();
    List bridges = new ArrayList();

    int va = 4;
    if ((flags & 2 /* FLAG_MARKERS */) != 0) {
      if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) {
        LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
        return null;
      }
      int count = ((IntConstant) bootstrapArgs.get(va++)).value;
      for (int i = 0; i < count; i++) {
        if (va >= bootstrapArgs.size()) {
          LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
          return null;
        }
        Value v = bootstrapArgs.get(va++);
        if (!(v instanceof ClassConstant)) {
          LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
          return null;
        }
        markerInterfaces.add((ClassConstant) v);
      }
    }

    if ((flags & 4 /* FLAG_BRIDGES */) != 0) {
      if (va == bootstrapArgs.size() || !(bootstrapArgs.get(va) instanceof IntConstant)) {
        LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
        return null;
      }
      int count = ((IntConstant) bootstrapArgs.get(va++)).value;
      for (int i = 0; i < count; i++) {
        if (va >= bootstrapArgs.size()) {
          LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
          return null;
        }
        Value v = bootstrapArgs.get(va++);
        if (!(v instanceof MethodType)) {
          LOGGER.warn("LambdaMetaFactory: unexpected arguments for LambdaMetaFactory.altMetaFactory");
          return null;
        }
        bridges.add((MethodType) v);
      }
    }

    List capTypes = Arrays.asList(invokedType).subList(0, invokedType.length - 1);
    if (!(invokedType[invokedType.length - 1] instanceof RefType)) {
      LOGGER.warn("unexpected interface type: " + invokedType[invokedType.length - 1]);
      return null;
    }
    SootClass functionalInterfaceToImplement = ((RefType) invokedType[invokedType.length - 1]).getSootClass();

    // Our thunk class implements the functional interface
    String enclosingClassname = enclosingClass.getName();
    String enclosingClassnamePrefix = null;
    if (enclosingClassname == null || enclosingClassname.equals("")) {
      enclosingClassnamePrefix = "soot.dummy.";
    } else {
      enclosingClassnamePrefix = enclosingClassname + "$";
    }

    String className;
    final boolean readableClassnames = true;
    if (readableClassnames) {
      // class names cannot contain <>
      String implMethodName = implMethod.getMethodRef().getName();
      String dummyName = "".equals(implMethodName) ? "init" : implMethodName;
      // XXX: $ causes confusion in inner class inference; remove for now
      dummyName = dummyName.replaceAll("\\$", "_");
      className = enclosingClassnamePrefix + dummyName + "__" + uniqSupply();
    } else {
      className = "soot.dummy.lambda" + uniqSupply();
    }
    SootClass tclass = Scene.v().makeSootClass(className);
    tclass.setModifiers(Modifier.PUBLIC | Modifier.FINAL);
    tclass.setSuperclass(Scene.v().getObjectType().getSootClass());
    tclass.addInterface(functionalInterfaceToImplement);

    // additions from altMetafactory
    if (serializable) {
      tclass.addInterface(RefType.v("java.io.Serializable").getSootClass());
    }
    for (int i = 0; i < markerInterfaces.size(); i++) {
      tclass.addInterface(((RefType) AsmUtil.toBaseType(markerInterfaces.get(i).getValue())).getSootClass());
    }

    // It contains fields for all the captures in the lambda
    List capFields = new ArrayList(capTypes.size());
    for (int i = 0; i < capTypes.size(); i++) {
      SootField f = Scene.v().makeSootField("cap" + i, capTypes.get(i), 0);
      capFields.add(f);
      tclass.addField(f);
    }

    // if the implMethod is a new private static in the enclosing class, make it public access so
    // it can be invoked from the thunk class
    if (MethodHandle.Kind.REF_INVOKE_STATIC.getValue() == implMethod.getKind()) {
      SootClass declClass = implMethod.getMethodRef().getDeclaringClass();
      if (declClass.getName().equals(enclosingClassname)) {
        SootMethod method = declClass.getMethod(implMethod.getMethodRef().getSubSignature());
        int modifiers = method.getModifiers() & ~Modifier.PRIVATE;
        modifiers = modifiers | Modifier.PUBLIC;
        method.setModifiers(modifiers);
      }
    }

    MethodSource ms = new ThunkMethodSource(capFields, samMethodType, implMethod, instantiatedMethodType);

    // Bootstrap method creates a new instance of this class
    SootMethod tboot = Scene.v().makeSootMethod("bootstrap$", capTypes, functionalInterfaceToImplement.getType(),
        Modifier.PUBLIC | Modifier.STATIC);
    tclass.addMethod(tboot);
    tboot.setSource(ms);

    // Constructor just copies the captures
    SootMethod tctor = Scene.v().makeSootMethod("", capTypes, VoidType.v(), Modifier.PUBLIC);
    tclass.addMethod(tctor);
    tctor.setSource(ms);

    // Dispatch runs the 'real' method implementing the body of the lambda
    addDispatch(name, tclass, samMethodType, instantiatedMethodType, capFields, implMethod);

    // For each bridge MethodType, add another dispatch method which calls the 'real' method
    for (int i = 0; i < bridges.size(); i++) {
      final MethodType bridgeType = bridges.get(i);
      addDispatch(name, tclass, bridgeType, instantiatedMethodType, capFields, implMethod);
    }

    Scene.v().addClass(tclass);
    if (enclosingClass.isApplicationClass()) {
      tclass.setApplicationClass();
    }

    for (SootMethod m : tclass.getMethods()) {
      // There is no reason not to load the bodies directly. After all,
      // we are introducing new classes while loading bodies.
      m.retrieveActiveBody();
    }

    // The hierarchy has to be rebuilt after adding the MetaFactory implementation.
    // soot.FastHierarchy.canStoreClass will otherwise fail due to not having an interval set for the class. This eventually
    // leads to the MetaFactory not being accepted as implementation of the functional interface it actually implements.
    // This, in turn, leads to missing edges in the call graph.
    Scene.v().releaseFastHierarchy();

    return tboot.makeRef();
  }

  private void addDispatch(String name, SootClass tclass, MethodType implMethodType, MethodType instantiatedMethodType,
      List capFields, MethodHandle implMethod) {
    ThunkMethodSource ms = new ThunkMethodSource(capFields, implMethodType, implMethod, instantiatedMethodType);
    SootMethod m = Scene.v().makeSootMethod(name, implMethodType.getParameterTypes(), implMethodType.getReturnType(),
        Modifier.PUBLIC);
    tclass.addMethod(m);
    m.setSource(ms);
  }

  private synchronized long uniqSupply() {
    return ++uniq;
  }

  private static class Wrapper {

    private Map wrapperTypes;
    /** valueOf(primitive) method signature */
    private Map valueOf;
    /** primitiveValue() method signature */
    private Map primitiveValue;

    public Wrapper() {
      PrimType[] tmp = { BooleanType.v(), ByteType.v(), CharType.v(), DoubleType.v(), FloatType.v(), IntType.v(),
          LongType.v(), ShortType.v() };
      wrapperTypes = new HashMap<>();
      valueOf = new HashMap<>();
      primitiveValue = new HashMap<>();
      for (PrimType primType : tmp) {
        RefType wrapperType = primType.boxedType();
        String cn = wrapperType.getClassName();

        wrapperTypes.put(wrapperType, primType);

        String valueOfMethodSignature = cn + " valueOf(" + primType.toString() + ")";
        SootMethod valueOfMethod = wrapperType.getSootClass().getMethod(valueOfMethodSignature);
        valueOf.put(primType, valueOfMethod);

        String primitiveValueMethodSignature = primType.toString() + " " + primType.toString() + "Value()";
        SootMethod primitiveValueMethod = wrapperType.getSootClass().getMethod(primitiveValueMethodSignature);
        primitiveValue.put(wrapperType, primitiveValueMethod);
      }
      wrapperTypes = Collections.unmodifiableMap(wrapperTypes);
      valueOf = Collections.unmodifiableMap(valueOf);
      primitiveValue = Collections.unmodifiableMap(primitiveValue);

    }

  }

  private class ThunkMethodSource implements MethodSource {
    /**
     * fields storing capture variables, in the order they appear in invokedType; to be prepended at target invocation site
     */
    private List capFields;
    /** MethodType of method to implemented by function object; either samMethodType or bridgeMethodType **/
    private MethodType implMethodType;
    /** implMethod - the MethodHandle providing the implementation */
    private MethodHandle implMethod;
    /** allows restrictions on invocation */
    private MethodType instantiatedMethodType;

    public ThunkMethodSource(List capFields, MethodType implMethodType, MethodHandle implMethod,
        MethodType instantiatedMethodType) {
      this.capFields = capFields;
      this.implMethodType = implMethodType;
      this.implMethod = implMethod;
      this.instantiatedMethodType = instantiatedMethodType;
    }

    @Override
    public Body getBody(SootMethod m, String phaseName) {
      if (!phaseName.equals("jb")) {
        throw new Error("unsupported body type: " + phaseName);
      }

      SootClass tclass = m.getDeclaringClass();
      JimpleBody jb = Jimple.v().newBody(m);

      if (m.getName().equals("")) {
        getInitBody(tclass, jb);
      } else if (m.getName().equals("bootstrap$")) {
        getBootstrapBody(tclass, jb);
      } else {
        getInvokeBody(tclass, jb);
      }

      // rename locals consistent with JimpleBodyPack
      LocalNameStandardizer.v().transform(jb);
      return jb;
    }

    /**
     * Thunk class init (constructor)
     *
     * @param tclass
     *          thunk class
     * @param jb
     */
    private void getInitBody(SootClass tclass, JimpleBody jb) {
      PatchingChain us = jb.getUnits();
      LocalGenerator lc = new LocalGenerator(jb);

      // @this
      Local l = lc.generateLocal(tclass.getType());
      us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newThisRef(tclass.getType())));

      // @parameters
      Chain capLocals = new HashChain<>();
      int i = 0;
      for (SootField f : capFields) {
        Local l2 = lc.generateLocal(f.getType());
        us.add(Jimple.v().newIdentityStmt(l2, Jimple.v().newParameterRef(f.getType(), i)));
        capLocals.add(l2);
        i++;
      }

      // super java.lang.Object.
      us.add(Jimple.v()
          .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l,
              Scene.v().makeConstructorRef(Scene.v().getObjectType().getSootClass(), Collections.emptyList()),
              Collections.emptyList())));

      // assign parameters to fields
      Iterator localItr = capLocals.iterator();
      for (SootField f : capFields) {
        Local l2 = localItr.next();
        us.add(Jimple.v().newAssignStmt(Jimple.v().newInstanceFieldRef(l, f.makeRef()), l2));
      }

      us.add(Jimple.v().newReturnVoidStmt());
    }

    private void getBootstrapBody(SootClass tclass, JimpleBody jb) {
      PatchingChain us = jb.getUnits();
      LocalGenerator lc = new LocalGenerator(jb);

      List capValues = new ArrayList();
      List capTypes = new ArrayList();
      int i = 0;
      for (SootField capField : capFields) {
        Type type = capField.getType();
        capTypes.add(type);
        Local p = lc.generateLocal(type);
        ParameterRef pref = Jimple.v().newParameterRef(type, i);
        us.add(Jimple.v().newIdentityStmt(p, pref));
        capValues.add(p);
        i++;
      }
      Local l = lc.generateLocal(tclass.getType());
      Value val = Jimple.v().newNewExpr(tclass.getType());
      us.add(Jimple.v().newAssignStmt(l, val));
      us.add(Jimple.v()
          .newInvokeStmt(Jimple.v().newSpecialInvokeExpr(l, Scene.v().makeConstructorRef(tclass, capTypes), capValues)));
      us.add(Jimple.v().newReturnStmt(l));
    }

    /**
     * Adds method which implements functional interface and invokes target implementation.
     *
     * @param tclass
     * @param jb
     */
    private void getInvokeBody(SootClass tclass, JimpleBody jb) {
      PatchingChain us = jb.getUnits();
      LocalGenerator lc = new LocalGenerator(jb);

      // @this
      Local this_ = lc.generateLocal(tclass.getType());
      us.add(Jimple.v().newIdentityStmt(this_, Jimple.v().newThisRef(tclass.getType())));

      // @parameter for direct arguments
      Chain samParamLocals = new HashChain<>();
      int i = 0;
      for (Type ty : implMethodType.getParameterTypes()) {
        Local l = lc.generateLocal(ty);
        us.add(Jimple.v().newIdentityStmt(l, Jimple.v().newParameterRef(ty, i)));
        samParamLocals.add(l);
        i++;
      }

      // narrowing casts to match instantiatedMethodType
      Iterator iptItr = instantiatedMethodType.getParameterTypes().iterator();
      Chain instParamLocals = new HashChain<>();
      for (Local l : samParamLocals) {
        Type ipt = iptItr.next();
        Local l2 = narrowingReferenceConversion(l, ipt, jb, us, lc);
        instParamLocals.add(l2);
      }

      List args = new ArrayList();

      // captured arguments
      for (SootField f : capFields) {
        Local l = lc.generateLocal(f.getType());
        us.add(Jimple.v().newAssignStmt(l, Jimple.v().newInstanceFieldRef(this_, f.makeRef())));
        args.add(l);
      }

      // direct arguments

      // The MethodHandle's first argument is the receiver, if it has one.
      // If there are no captured arguments, use the first parameter as the receiver.
      int kind = implMethod.getKind();
      boolean needsReceiver = false;
      if (MethodHandle.Kind.REF_INVOKE_INTERFACE.getValue() == kind
          || MethodHandle.Kind.REF_INVOKE_VIRTUAL.getValue() == kind
          || MethodHandle.Kind.REF_INVOKE_SPECIAL.getValue() == kind) {
        // NOTE: for a method reference to a constructor, the receiver is not needed because it's the new object
        needsReceiver = true;
      }
      Iterator iplItr = instParamLocals.iterator();
      if (capFields.size() == 0 && iplItr.hasNext() && needsReceiver) {
        RefType receiverType = implMethod.getMethodRef().getDeclaringClass().getType();
        Local l = adapt(iplItr.next(), receiverType, jb, us, lc);
        args.add(l);
      }

      int j = args.size();
      if (needsReceiver) {
        // assert: if there is a receiver, it is already filled, but the alignment to parameters is off by 1
        j = args.size() - 1;
      }
      while (iplItr.hasNext()) {
        Local pl = iplItr.next();

        Type to = implMethod.getMethodRef().getParameterType(j);

        Local l = adapt(pl, to, jb, us, lc);
        args.add(l);
        j++;
      }

      invokeImplMethod(jb, us, lc, args);
    }

    private Local adapt(Local fromLocal, Type to, JimpleBody jb, PatchingChain us, LocalGenerator lc) {

      Type from = fromLocal.getType();

      // Implements JLS 5.3 Method Invocation Context for adapting arguments from lambda expression to
      // formal arguments of target implementation

      // an identity conversion (§5.1.1)
      if (from.equals(to)) {
        return fromLocal;
      }

      if (from instanceof ArrayType) {
        return wideningReferenceConversion(fromLocal);
      }

      if (from instanceof RefType && to instanceof RefType) {
        return wideningReferenceConversion(fromLocal);
      }

      if (from instanceof PrimType) {
        if (to instanceof PrimType) {
          // a widening primitive conversion (§5.1.2)
          return wideningPrimitiveConversion(fromLocal, to, jb, us, lc);
        } else {
          // a boxing conversion (§5.1.7)
          // a boxing conversion followed by widening reference conversion

          // from is PrimType
          // to is RefType
          Local boxed = box(fromLocal, jb, us, lc);
          return wideningReferenceConversion(boxed);
        }
      } else {
        // an unboxing conversion (§5.1.8)
        // an unboxing conversion followed by a widening primitive conversion

        // from is RefType
        // to is PrimType
        if (!(to instanceof PrimType)) {
          throw new IllegalArgumentException("Expected 'to' to be a PrimType");
        }

        Local unboxed = unbox(fromLocal, jb, us, lc);
        return wideningPrimitiveConversion(unboxed, to, jb, us, lc);
      }
    }

    /**
     * P box = P.valueOf(fromLocal);
     *
     * @param fromLocal
     *          primitive
     * @param jb
     * @param us
     * @return
     */
    private Local box(Local fromLocal, JimpleBody jb, PatchingChain us, LocalGenerator lc) {
      PrimType primitiveType = (PrimType) fromLocal.getType();
      RefType wrapperType = primitiveType.boxedType();

      SootMethod valueOfMethod = wrapper.valueOf.get(primitiveType);

      Local lBox = lc.generateLocal(wrapperType);
      if (lBox == null || valueOfMethod == null || us == null) {
        throw new NullPointerException(String.format("%s,%s,%s,%s", valueOfMethod, primitiveType, wrapper.valueOf.entrySet(),
            wrapper.valueOf.get(primitiveType)));
      }
      us.add(Jimple.v().newAssignStmt(lBox, Jimple.v().newStaticInvokeExpr(valueOfMethod.makeRef(), fromLocal)));

      return lBox;
    }

    /**
     * p unbox = fromLocal.pValue();
     *
     * @param fromLocal
     *          boxed
     * @param jb
     * @param us
     * @return
     */
    private Local unbox(Local fromLocal, JimpleBody jb, PatchingChain us, LocalGenerator lc) {
      RefType wrapperType = (RefType) fromLocal.getType();
      PrimType primitiveType = wrapper.wrapperTypes.get(wrapperType);

      SootMethod primitiveValueMethod = wrapper.primitiveValue.get(wrapperType);

      Local lUnbox = lc.generateLocal(primitiveType);
      us.add(Jimple.v().newAssignStmt(lUnbox, Jimple.v().newVirtualInvokeExpr(fromLocal, primitiveValueMethod.makeRef())));

      return lUnbox;
    }

    private Local wideningReferenceConversion(Local fromLocal) {
      // a widening reference conversion (JLS §5.1.5)
      // TODO: confirm that 'from' is a subtype of 'to'
      return fromLocal;
    }

    /**
     * T t = (T) fromLocal;
     *
     * @param fromLocal
     * @param to
     * @param jb
     * @param us
     * @return
     */
    private Local narrowingReferenceConversion(Local fromLocal, Type to, JimpleBody jb, PatchingChain us,
        LocalGenerator lc) {
      if (fromLocal.getType().equals(to)) {
        return fromLocal;
      }

      if (!(fromLocal.getType() instanceof RefType || fromLocal.getType() instanceof ArrayType)) {
        return fromLocal;
      }
      // throw new IllegalArgumentException("Expected source to have reference type");
      if (!(to instanceof RefType || to instanceof ArrayType)) {
        return fromLocal;
        // throw new IllegalArgumentException("Expected target to have reference type");
      }

      Local l2 = lc.generateLocal(to);
      us.add(Jimple.v().newAssignStmt(l2, Jimple.v().newCastExpr(fromLocal, to)));
      return l2;
    }

    /**
     * T t = (T) fromLocal;
     *
     * @param fromLocal
     * @param to
     * @param jb
     * @param us
     * @return
     */
    private Local wideningPrimitiveConversion(Local fromLocal, Type to, JimpleBody jb, PatchingChain us,
        LocalGenerator lc) {
      if (!(fromLocal.getType() instanceof PrimType)) {
        throw new IllegalArgumentException("Expected source to have primitive type");
      }
      if (!(to instanceof PrimType)) {
        throw new IllegalArgumentException("Expected target to have primitive type");
      }

      Local l2 = lc.generateLocal(to);
      us.add(Jimple.v().newAssignStmt(l2, Jimple.v().newCastExpr(fromLocal, to)));
      return l2;
    }

    /**
     * Invocation of target implementation method.
     *
     * @param jb
     * @param us
     * @param args
     */
    private void invokeImplMethod(JimpleBody jb, PatchingChain us, LocalGenerator lc, List args) {
      Value value = _invokeImplMethod(jb, us, lc, args);

      if (value instanceof InvokeExpr && soot.VoidType.v().equals(implMethod.getMethodRef().getReturnType())) {
        // implementation method is void
        us.add(Jimple.v().newInvokeStmt(value));
        us.add(Jimple.v().newReturnVoidStmt());
      } else if (soot.VoidType.v().equals(implMethodType.getReturnType())) {
        // dispatch method is void
        us.add(Jimple.v().newInvokeStmt(value));
        us.add(Jimple.v().newReturnVoidStmt());
      } else {
        // neither is void, must pass through return value

        Local ret = lc.generateLocal(value.getType());
        us.add(Jimple.v().newAssignStmt(ret, value));

        // adapt return value
        Local retAdapted = adapt(ret, implMethodType.getReturnType(), jb, us, lc);
        us.add(Jimple.v().newReturnStmt(retAdapted));
      }
    }

    private Value _invokeImplMethod(JimpleBody jb, PatchingChain us, LocalGenerator lc, List args) {
      // A lambda capturing 'this' may be implemented by a private instance method.
      // A method reference to an instance method may be implemented by the instance method itself.
      // To use the correct invocation style, resolve the method and determine how the compiler
      // implemented the lambda or method reference.

      SootMethodRef methodRef = implMethod.getMethodRef();
      MethodHandle.Kind k = MethodHandle.Kind.getKind(implMethod.getKind());
      switch (k) {
        case REF_INVOKE_STATIC:
          return Jimple.v().newStaticInvokeExpr(methodRef, args);
        case REF_INVOKE_INTERFACE:
          return Jimple.v().newInterfaceInvokeExpr(args.get(0), methodRef, rest(args));
        case REF_INVOKE_VIRTUAL:
          return Jimple.v().newVirtualInvokeExpr(args.get(0), methodRef, rest(args));
        case REF_INVOKE_SPECIAL:
          final SootClass currentClass = jb.getMethod().getDeclaringClass();
          final SootClass calledClass = methodRef.getDeclaringClass();
          // It can be the case that the method is not in the same class (or a super class).
          // As such, we need a virtual call in these cases.
          if (Scene.v().getOrMakeFastHierarchy().canStoreClass(currentClass, calledClass)) {
            return Jimple.v().newSpecialInvokeExpr(args.get(0), methodRef, rest(args));
          } else {
            SootMethod m = implMethod.getMethodRef().resolve();
            if (!m.isPublic()) {
              // make sure the method is public
              int mod = Modifier.PUBLIC | m.getModifiers();
              mod &= ~Modifier.PRIVATE;
              mod &= ~Modifier.PROTECTED;
              m.setModifiers(mod);
            }
            return Jimple.v().newVirtualInvokeExpr(args.get(0), methodRef, rest(args));
          }
        case REF_INVOKE_CONSTRUCTOR:
          RefType type = methodRef.getDeclaringClass().getType();
          NewExpr newRef = Jimple.v().newNewExpr(type);
          Local newLocal = lc.generateLocal(type);
          us.add(Jimple.v().newAssignStmt(newLocal, newRef));
          // NOTE: args does not include the receiver
          SpecialInvokeExpr specialInvokeExpr = Jimple.v().newSpecialInvokeExpr(newLocal, methodRef, args);
          InvokeStmt invokeStmt = Jimple.v().newInvokeStmt(specialInvokeExpr);
          us.add(invokeStmt);

          return newLocal;
        case REF_GET_FIELD:
        case REF_GET_FIELD_STATIC:
        case REF_PUT_FIELD:
        case REF_PUT_FIELD_STATIC:
        default:
      }
      throw new IllegalArgumentException("Unexpected MethodHandle.Kind " + implMethod.getKind());
    }

    private List rest(List args) {
      int first = 1;
      int last = args.size();
      if (last < first) {
        return Collections.emptyList();
      }
      return args.subList(first, last);
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy