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

org.robovm.compiler.plugin.invokedynamic.lambda.LambdaPlugin Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014 RoboVM AB
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * 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 Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.robovm.compiler.plugin.invokedynamic.lambda;

import org.apache.commons.io.FileUtils;
import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.Types;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin;
import soot.*;
import soot.jimple.*;
import soot.tagkit.LineNumberTag;
import soot.util.Switch;

import java.io.File;
import java.io.IOException;
import java.util.*;

public class LambdaPlugin implements InvokeDynamicCompilerPlugin.Delegate {
    static final int BRIDGE = 0x00000040;
    static final int SYNTHETIC = 0x00001000;
    
    private static int FLAG_MARKERS = 2;
    private static int FLAG_BRIDGES = 4;
    private int tmpCounter = 0;

    final Map generators = new HashMap();

    private static boolean isLambdaBootstrapMethod(SootMethodRef methodRef) {
        return methodRef.declaringClass().getName().equals("java.lang.invoke.LambdaMetafactory")
                && (methodRef.name().equals("metafactory") || methodRef.name().equals("altMetafactory"));
    }

    @Override
    public void afterClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException {
        synchronized (generators) {
            generators.remove(clazz.getSootClass());
        }
    }

    @Override
    public void beforeMethod(Config config, Clazz clazz, SootMethod method, ModuleBuilder moduleBuilder) throws IOException {
        tmpCounter = 0;
    }

    @Override
    public LinkedList transformDynamicInvoke(Config config, Clazz clazz, SootClass sootClass,
            SootMethod method, DefinitionStmt defStmt, DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException {

        LinkedList newUnits = null;
        if (isLambdaBootstrapMethod(invokeExpr.getBootstrapMethodRef())) {
            Body body = method.retrieveActiveBody();
            PatchingChain units = body.getUnits();
            LambdaClassGenerator generator = null;
            synchronized (generators) {
                generator = generators.get(sootClass);
                if (generator == null) {
                    generator = new LambdaClassGenerator();
                    generators.put(sootClass, generator);
                }
            }

            List bsmArgs = invokeExpr.getBootstrapArgs();
            SootClass caller = sootClass;
            String invokedName = invokeExpr.getMethodRef().name();
            SootMethodRef invokedType = invokeExpr.getMethodRef();
            SootMethodType samMethodType = (SootMethodType) bsmArgs.get(0);
            SootMethodHandle implMethod = (SootMethodHandle) bsmArgs.get(1);
            SootMethodType instantiatedMethodType = (SootMethodType) bsmArgs.get(2);

            LambdaClass callSite;
            List markerInterfaces = new ArrayList<>();
            List bridgeMethods = new ArrayList<>();
            if (invokeExpr.getBootstrapMethodRef().name().equals("altMetafactory")) {
                int flags = ((IntConstant) bsmArgs.get(3)).value;
                int bsmArgsIdx = 4;
                if ((flags & FLAG_MARKERS) > 0) {
                    int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value;
                    for (int i = 0; i < count; i++) {
                        Object value = bsmArgs.get(bsmArgsIdx++);
                        if (value instanceof Type) {
                            markerInterfaces.add((Type) value);
                        } else if (value instanceof ClassConstant) {
                            String className = ((ClassConstant) value).getValue().replace('/', '.');
                            markerInterfaces.add(SootResolver.v()
                                    .resolveClass(className, SootClass.HIERARCHY).getType());
                        }
                    }
                }
                if ((flags & FLAG_BRIDGES) > 0) {
                    int count = ((IntConstant) bsmArgs.get(bsmArgsIdx++)).value;
                    for (int i = 0; i < count; i++) {
                        bridgeMethods.add((SootMethodType) bsmArgs.get(bsmArgsIdx++));
                    }
                }
            }

            // search for additional bridge methods in the
            // interface we implement. Javac
            // may not emit them in the invoke dynamic call
            // see issue #1087
            if (bridgeMethods.size() == 0) {
                SootClass targetType = SootResolver.v().resolveClass(
                        invokedType.returnType().toString().replace('/', '.'), SootClass.SIGNATURES);
                String samDescriptor = Types.getDescriptor(samMethodType.getParameterTypes(),
                        samMethodType.getReturnType());
                for (SootMethod targetTypeMethod : targetType.getMethods()) {
                    boolean isBridgeMethod = targetTypeMethod.getName().equals(invokedName);
                    isBridgeMethod &= targetTypeMethod.getParameterCount() == samMethodType.getParameterTypes().size();
                    isBridgeMethod &= ((targetTypeMethod.getModifiers() & BRIDGE) != 0);
                    isBridgeMethod &= ((targetTypeMethod.getModifiers() & SYNTHETIC) != 0);
                    if(isBridgeMethod) {
                        String targetTypeMethodDesc = Types.getDescriptor(targetTypeMethod);
                        if (!targetTypeMethodDesc.equals(samDescriptor)) {
                            bridgeMethods.add(new BridgeMethodType(targetTypeMethod.getReturnType(),
                                    targetTypeMethod.getParameterTypes()));
                        }
                    }
                }
            }

            // generate the lambda class
            callSite = generator.generate(caller, invokedName, invokedType, samMethodType, implMethod,
                    instantiatedMethodType, markerInterfaces, bridgeMethods);
            File f = clazz.getPath().getGeneratedClassFile(callSite.getLambdaClassName());
            FileUtils.writeByteArrayToFile(f, callSite.getClassData());
            // The lambda class is created after the caller is
            // compiled.
            // This prevents the triggering of a recompile of
            // the caller.
            f.setLastModified(clazz.lastModified());

            SootClass lambdaClass = SootResolver.v()
                    .makeClassRef(callSite.getLambdaClassName().replace('/', '.'));

            Local l = (Local) defStmt.getLeftOp();
            Type samType = callSite.getTargetMethodReturnType();
            newUnits = new LinkedList<>();
            if (callSite.getTargetMethodName().equals("")) {
                // Constant lambda. Create an instance once and
                // reuse for
                // every call.
                String fieldName = lambdaClass.getName()
                        .substring(lambdaClass.getName().lastIndexOf('.') + 1);
                SootField field = new SootField(fieldName, lambdaClass.getType(),
                        Modifier.STATIC | Modifier.PRIVATE | Modifier.TRANSIENT
                                | 0x1000 /* SYNTHETIC */);
                method.getDeclaringClass().addField(field);
                // l = LambdaClass.lambdaField
                newUnits.add(
                        Jimple.v().newAssignStmt(l, Jimple.v().newStaticFieldRef(field.makeRef())));
                // if l != null goto succOfInvokedynamic
                newUnits.add(Jimple.v().newIfStmt(Jimple.v().newNeExpr(l, NullConstant.v()),
                        units.getSuccOf(defStmt)));
                // $tmpX = new LambdaClass()
                Local tmp = Jimple.v().newLocal("$tmp_lambda" + (tmpCounter++), lambdaClass.getType());
                body.getLocals().add(tmp);
                newUnits.add(
                        Jimple.v().newAssignStmt(tmp, Jimple.v().newNewExpr(lambdaClass.getType())));
                newUnits.add(Jimple.v().newInvokeStmt(Jimple.v().newSpecialInvokeExpr(tmp,
                        Scene.v().makeConstructorRef(lambdaClass, Collections. emptyList()))));
                // LambdaClass.lambdaField = $tmpX
                newUnits.add(
                        Jimple.v().newAssignStmt(Jimple.v().newStaticFieldRef(field.makeRef()), tmp));
                // l = $tmpX
                newUnits.add(Jimple.v().newAssignStmt(l, tmp));
            } else {
                // Static factory method returns the lambda to
                // use.
                newUnits.add(Jimple.v().newAssignStmt(l,
                        Jimple.v().newStaticInvokeExpr(
                                Scene.v().makeMethodRef(lambdaClass,
                                        callSite.getTargetMethodName(),
                                        callSite.getTargetMethodParameters(),
                                        samType, true),
                                invokeExpr.getArgs())));
            }

            // dkimitsa: attach LineNumberTag to all new units that are inserted instead of DynamicInvokeExpr
            // as it would break variable resolution in debugger information plugin
            for (Object o : defStmt.getTags()) {
                if (o instanceof LineNumberTag) {
                    // attach same line number to all units in new units
                    LineNumberTag ln = (LineNumberTag) o;
                    for (Unit u : newUnits)
                        u.addTag(new LineNumberTag(ln.getLineNumber()));
                    break;
                }
            }
        }

        return newUnits;
    }
    
    static class BridgeMethodType extends Constant implements SootMethodType {
        private static final long serialVersionUID = 1L;

        private final Type returnType;
        private final List parameterTypes;

        public BridgeMethodType(Type returnType, List parameterTypes) {
            this.returnType = returnType;
            this.parameterTypes = parameterTypes;
        }

        public Type getReturnType() {
            return returnType;
        }

        public List getParameterTypes() {
            return Collections.unmodifiableList(parameterTypes);
        }

        @Override
        public Type getType() {
            return RefType.v("java.lang.invoke.MethodType");
        }

        @Override
        public void apply(Switch sw) {
            ((ConstantSwitch) sw).defaultCase(this);
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();

            sb.append('(');
            for (Iterator it = parameterTypes.iterator(); it.hasNext();) {
                sb.append(it.next());
                if (it.hasNext()) {
                    sb.append(',');
                }
            }
            return sb.append(')').append(returnType).toString();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy