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

org.robovm.compiler.plugin.objc.ObjCProtocolToObjCObjectPlugin 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.objc;

import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.clazz.Clazz;
import org.robovm.compiler.config.Config;
import org.robovm.compiler.log.Logger;
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.CompilerPlugin;
import soot.*;
import soot.jimple.InvokeStmt;
import soot.jimple.SpecialInvokeExpr;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * {@link CompilerPlugin} which replaces Object super class with NSObject for
 * local/nested/anonymous classes that implement NSObjectProtocol interfaces.
 * Otherwise marshalling of pure java object will fail with exception
 */
public class ObjCProtocolToObjCObjectPlugin extends AbstractCompilerPlugin {
    public static final String OBJC_PROTOCOL = "org.robovm.objc.ObjCProtocol";
    public static final String NS_OBJECT = "org.robovm.apple.foundation.NSObject";
    public static final String OBJC_OBJECT = "org.robovm.objc.ObjCObject";
    private static final int SYNTHETIC = 0x00001000;
    private SootClass org_robovm_apple_foundation_NSObject = null;
    private SootClass org_robovm_objc_ObjCObject = null;
    private SootClass org_robovm_objc_ObjCProtocol = null;

    private boolean initialized = false;

    private void init() {
        if (initialized) {
            return;
        }
        SootResolver r = SootResolver.v();
        org_robovm_objc_ObjCProtocol = r.makeClassRef(OBJC_PROTOCOL);
        org_robovm_apple_foundation_NSObject = r.makeClassRef(NS_OBJECT);
        // These have to be resolved to HIERARCHY so that isPhantom() works
        // properly
        org_robovm_objc_ObjCObject = r.resolveClass(OBJC_OBJECT, SootClass.HIERARCHY);
        initialized = true;
    }

    private boolean shouldReplaceSuper(SootClass cls) {
        if (org_robovm_objc_ObjCProtocol.isPhantom() || !cls.isConcrete())
            return false;

        if (!cls.hasSuperclass() || !"java.lang.Object".equals(cls.getSuperclass().getName()))
            return false;

        return inheritsObjCProtocol(cls);
    }

    private boolean inheritsObjCProtocol(SootClass cls) {
        for (SootClass interfaze : cls.getInterfaces()) {
            if (interfaze == org_robovm_objc_ObjCProtocol || inheritsObjCProtocol(interfaze)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns class the implementation should have as superclass, either
     * org.robovm.apple.foundation.NSObject,
     * org.robovm.objc.ObjCObject.
     */
    private SootClass getSuperReplacementCandidate(SootClass cls) {
        if (cls.getName().equals("org.robovm.apple.foundation.NSObjectProtocol")) {
            return org_robovm_apple_foundation_NSObject;
        }
        if (cls == org_robovm_objc_ObjCProtocol) {
            return org_robovm_objc_ObjCObject;
        }
        for (SootClass interfaze : cls.getInterfaces()) {
            SootClass superCls = getSuperReplacementCandidate(interfaze);
            if (superCls != null) {
                return superCls;
            }
        }
        return null;
    }

    /**
     * @return true if super call was adjusted
     */
    private boolean adjustSuperInitCall(Logger logger, SootClass cls, SootClass newSuper) {
        // replacing only java.lang.Object super calls
        // skipping synthetic constructors and constructors that calls "this". instead of supper
        // single kind of super call () so call directly
        List constructors = cls.getMethods().stream()
                .filter((m) -> (m.getModifiers() & SYNTHETIC) == 0 && m.getName().equals(""))
                .collect(Collectors.toList());
        // getting all expressions for modification
        List expressionsToAdjust = new ArrayList<>();

        for (SootMethod method: constructors) {
            // find first SpecialInvokeExpr
            Body body = method.retrieveActiveBody();
            PatchingChain units = body.getUnits();
            SpecialInvokeExpr expr = units.stream()
                    .filter(e -> e instanceof InvokeStmt && ((InvokeStmt) e).getInvokeExpr() instanceof SpecialInvokeExpr)
                    .map(e -> (SpecialInvokeExpr) ((InvokeStmt) e).getInvokeExpr())
                    .findFirst()
                    .orElse(null);
            if (expr != null) {
                if (expr.getMethodRef().getSignature().equals("()>")) {
                    expressionsToAdjust.add(expr);
                } else if (expr.getMethodRef().declaringClass() != cls) {
                    // skip "this". calls but fails on other class init call
                    logger.warn("ObjCProtocol to NSObject failed: missing ()> call in %s", cls.getName());
                    return false;
                }
            }
        }

        // patch collected expression
        SootMethodRef replacement = newSuper.getMethod("void ()").makeRef();
        for (SpecialInvokeExpr expr: expressionsToAdjust)
            expr.setMethodRef(replacement);

        return true;
    }

    @Override
    public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) {
        init();
        SootClass sootClass = clazz.getSootClass();
        if (shouldReplaceSuper(sootClass)) {
            SootClass superCandidate = getSuperReplacementCandidate(sootClass);
            if (superCandidate != null) {
                if (adjustSuperInitCall(config.getLogger(), sootClass, superCandidate))
                    sootClass.setSuperclass(superCandidate);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy