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

org.robovm.compiler.plugin.invokedynamic.InvokeDynamicCompilerPlugin Maven / Gradle / Ivy

The newest version!
package org.robovm.compiler.plugin.invokedynamic;

import org.robovm.compiler.CompilerException;
import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.invokedynamic.lambda.LambdaPlugin;
import org.robovm.compiler.plugin.invokedynamic.stringconcat.StringConcatRewriterPlugin;
import soot.*;
import soot.jimple.*;

import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

public class InvokeDynamicCompilerPlugin extends AbstractCompilerPlugin {

    /**
     * Delegate for specific bootstrap method handler
     * (specific implementation to be done there)
     */
    public interface Delegate {
        default void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException {
        }

        default void beforeMethod(Config config, Clazz clazz, SootMethod method, ModuleBuilder moduleBuilder) throws IOException {
        }

        default void afterClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException {
        }

        LinkedList transformDynamicInvoke(
                Config config, Clazz clazz, SootClass sootClass, SootMethod method, DefinitionStmt defStmt,
                DynamicInvokeExpr invokeExpr, ModuleBuilder moduleBuilder) throws IOException;
    }

    private final List supportedDynamicInvokes;

    public InvokeDynamicCompilerPlugin() {
        supportedDynamicInvokes = List.of(
                new LambdaPlugin(),
                new StringConcatRewriterPlugin(),
                new UnrecognizedBootstrapDelegate() // has to be declared last !
        );
    }

    @Override
    public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException {
        SootClass sootClass = clazz.getSootClass();

        // deliver beforeClass notification to allow delegates to initializes
        for (Delegate delegate : supportedDynamicInvokes)
            delegate.beforeClass(config, clazz, moduleBuilder);

        for (SootMethod method : sootClass.getMethods()) {
            // deliver beforeMethod notification to allow delegates to reset counters whatever
            for (Delegate delegate : supportedDynamicInvokes)
                delegate.beforeMethod(config, clazz, method, moduleBuilder);

            transformMethod(config, clazz, sootClass, method, moduleBuilder);
        }

        // deliver afterClass notification to allow delegates to reset class related activities
        for (Delegate delegate : supportedDynamicInvokes)
            delegate.afterClass(config, clazz, moduleBuilder);
    }

    private void transformMethod(Config config, Clazz clazz, SootClass sootClass,
                                 SootMethod method, ModuleBuilder moduleBuilder) throws IOException {
        if (!method.isConcrete())
            return;

        Body body = method.retrieveActiveBody();
        PatchingChain units = body.getUnits();
        for (Unit unit = units.getFirst(); unit != null; unit = body.getUnits().getSuccOf(unit)) {
            if (unit instanceof DefinitionStmt) {
                DefinitionStmt defStmt = (DefinitionStmt) unit;
                if (defStmt.getRightOp() instanceof DynamicInvokeExpr) {
                    DynamicInvokeExpr invokeExpr = (DynamicInvokeExpr) ((DefinitionStmt) unit).getRightOp();
                    LinkedList newUnits = null;
                    try {
                        for (Delegate delegate : supportedDynamicInvokes) {
                            newUnits = delegate.transformDynamicInvoke(config, clazz, sootClass, method, defStmt,
                                    invokeExpr, moduleBuilder);
                            if (newUnits != null)
                                break;
                        }

                        // should not happen as there is fallback
                        assert newUnits != null;

                        units.insertAfter(newUnits, unit);
                        units.remove(unit);
                        unit = newUnits.getLast();

                    } catch (Throwable e) {
                        // TODO: Change the jimple of the method to throw a
                        // LambdaConversionException at runtime.
                        throw new CompilerException(e);
                    }
                }
            }
        }
    }

    /**
     * Fallback that will insert NoSuchMethodError exception for any attempt to invoke
     */
    private static class UnrecognizedBootstrapDelegate implements Delegate {
        private int tmpCounter = 0;

        private boolean initialized = false;
        private SootClass java_lang_NoSuchMethodError;
        private SootMethodRef java_lang_NoSuchMethodError_init;

        /**
         * performs lazy initializations.
         * can't perform it in constructor as config and plugins are being created before soot is
         * initialized and it will perform G.reset() which causes VoidType.v() to be not equals as received
         * from different globals. It seems to be a wide project bug in Soot's equal implementations.
         * Check VoidType.equals for example
         */
        private void initializeIfRequired() {
            if (initialized)
                return;

            SootResolver r = SootResolver.v();
            SootClass java_lang_String = r.makeClassRef("java.lang.String");

            java_lang_NoSuchMethodError = r.makeClassRef("java.lang.NoSuchMethodError");
            java_lang_NoSuchMethodError_init =
                    Scene.v().makeMethodRef(
                            java_lang_NoSuchMethodError,
                            "",
                            Collections.singletonList(java_lang_String.getType()),
                            VoidType.v(), false);
            initialized = true;
        }

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

        @Override
        public LinkedList transformDynamicInvoke(
                Config config, Clazz clazz, SootClass sootClass, SootMethod method,
                DefinitionStmt defStmt, DynamicInvokeExpr invokeExpr,
                ModuleBuilder moduleBuilder)
        {
            initializeIfRequired();
            String msg = "Unsupported InvokeDynamic to " + invokeExpr.getBootstrapMethodRef().declaringClass().getName() +
                    '.' + invokeExpr.getBootstrapMethodRef().name();
            Jimple jimple = Jimple.v();
            Body body = method.retrieveActiveBody();
            LinkedList newUnits = new LinkedList<>();

            // initialize left size of expression just to keep a variable in scope
            Type localType = defStmt.getLeftOp().getType();
            Value dummyConstant;
            if (localType instanceof RefLikeType)
                dummyConstant = NullConstant.v();
            else if (localType instanceof IntegerType)
                dummyConstant = IntConstant.v(0);
            else if (localType instanceof LongType)
                dummyConstant = LongConstant.v(0);
            else if (localType instanceof FloatType)
                dummyConstant = FloatConstant.v(0);
            else if (localType instanceof DoubleType)
                dummyConstant = DoubleConstant.v(0);
            else throw new IllegalStateException("Unexpected local type " + localType);
            newUnits.add(jimple.newAssignStmt(defStmt.getLeftOp(), dummyConstant));

            // insert exception
            Local exc = jimple.newLocal("$tmp_invdyn_exc" + (tmpCounter++), java_lang_NoSuchMethodError.getType());
            body.getLocals().add(exc);
            newUnits.add(jimple.newAssignStmt(exc, jimple.newNewExpr(java_lang_NoSuchMethodError.getType())));
            newUnits.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(exc, java_lang_NoSuchMethodError_init,
                    StringConstant.v(msg))));
            newUnits.add(jimple.newThrowStmt(exc));

            return newUnits;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy