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

org.robovm.compiler.plugin.objc.ObjCMemberPlugin 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.apache.commons.lang3.StringUtils;
import org.robovm.compiler.AbstractMethodCompiler;
import org.robovm.compiler.Annotations;
import org.robovm.compiler.Annotations.Visibility;
import org.robovm.compiler.CompilerException;
import org.robovm.compiler.Functions;
import org.robovm.compiler.Linker;
import org.robovm.compiler.MarshalerLookup.MarshalSite;
import org.robovm.compiler.MarshalerLookup.MarshalerMethod;
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.llvm.Bitcast;
import org.robovm.compiler.llvm.BooleanConstant;
import org.robovm.compiler.llvm.Call;
import org.robovm.compiler.llvm.ConstantGetelementptr;
import org.robovm.compiler.llvm.Function;
import org.robovm.compiler.llvm.Global;
import org.robovm.compiler.llvm.IntegerConstant;
import org.robovm.compiler.llvm.Inttoptr;
import org.robovm.compiler.llvm.PointerType;
import org.robovm.compiler.llvm.Ptrtoint;
import org.robovm.compiler.llvm.Ret;
import org.robovm.compiler.llvm.StructureType;
import org.robovm.compiler.llvm.Variable;
import org.robovm.compiler.llvm.ZeroInitializer;
import org.robovm.compiler.plugin.AbstractCompilerPlugin;
import org.robovm.compiler.plugin.CompilerPlugin;
import org.robovm.compiler.target.framework.FrameworkTarget;
import org.robovm.compiler.util.generic.SootClassUtils;
import org.robovm.compiler.util.generic.SootMethodType;
import soot.Body;
import soot.BooleanType;
import soot.DoubleType;
import soot.FloatType;
import soot.IntType;
import soot.Local;
import soot.LongType;
import soot.Modifier;
import soot.PatchingChain;
import soot.PrimType;
import soot.RefLikeType;
import soot.RefType;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootFieldRef;
import soot.SootMethod;
import soot.SootMethodRef;
import soot.SootResolver;
import soot.Type;
import soot.Unit;
import soot.Value;
import soot.VoidType;
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.LongConstant;
import soot.jimple.NopStmt;
import soot.jimple.NullConstant;
import soot.jimple.StaticInvokeExpr;
import soot.jimple.Stmt;
import soot.jimple.StringConstant;
import soot.tagkit.AnnotationStringElem;
import soot.tagkit.AnnotationTag;
import soot.tagkit.SignatureTag;
import soot.util.Chain;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

import static org.robovm.compiler.Annotations.BRIDGE;
import static org.robovm.compiler.Annotations.CALLBACK;
import static org.robovm.compiler.Annotations.MARSHALER;
import static org.robovm.compiler.Annotations.addRuntimeVisibleAnnotation;
import static org.robovm.compiler.Annotations.copyParameterAnnotations;
import static org.robovm.compiler.Annotations.getAnnotation;
import static org.robovm.compiler.Annotations.hasAnnotation;
import static org.robovm.compiler.Annotations.hasMachineSizedFloatAnnotation;
import static org.robovm.compiler.Annotations.hasMachineSizedSIntAnnotation;
import static org.robovm.compiler.Annotations.hasMachineSizedUIntAnnotation;
import static org.robovm.compiler.Annotations.hasParameterAnnotation;
import static org.robovm.compiler.Annotations.hasPointerAnnotation;
import static org.robovm.compiler.Annotations.readBooleanElem;
import static org.robovm.compiler.Annotations.readStringElem;
import static soot.Modifier.FINAL;
import static soot.Modifier.NATIVE;
import static soot.Modifier.PRIVATE;
import static soot.Modifier.STATIC;

/**
 * {@link CompilerPlugin} which transforms Objective-C methods and properties to @Bridge
 * methods. Also adds corresponding @Callback methods for each method and
 * property.
 */
public class ObjCMemberPlugin extends AbstractCompilerPlugin {
    public static final String OBJC_ANNOTATIONS_PACKAGE = "org/robovm/objc/annotation";
    public static final String METHOD = "L" + OBJC_ANNOTATIONS_PACKAGE + "/Method;";
    public static final String PROPERTY = "L" + OBJC_ANNOTATIONS_PACKAGE + "/Property;";
    public static final String BIND_SELECTOR = "L" + OBJC_ANNOTATIONS_PACKAGE + "/BindSelector;";
    public static final String NOT_IMPLEMENTED = "L" + OBJC_ANNOTATIONS_PACKAGE + "/NotImplemented;";
    public static final String IBACTION = "L" + OBJC_ANNOTATIONS_PACKAGE + "/IBAction;";
    public static final String IBOUTLET = "L" + OBJC_ANNOTATIONS_PACKAGE + "/IBOutlet;";
    public static final String IBINSPECTABLE = "L" + OBJC_ANNOTATIONS_PACKAGE + "/IBInspectable;";
    public static final String IBOUTLETCOLLECTION = "L" + OBJC_ANNOTATIONS_PACKAGE + "/IBOutletCollection;";
    public static final String NATIVE_CLASS = "L" + OBJC_ANNOTATIONS_PACKAGE + "/NativeClass;";
    public static final String CUSTOM_CLASS = "L" + OBJC_ANNOTATIONS_PACKAGE + "/CustomClass;";
    public static final String NATIVE_PROTOCOL_PROXY = "L" + OBJC_ANNOTATIONS_PACKAGE + "/NativeProtocolProxy;";
    public static final String TYPE_ENCODING = "L" + OBJC_ANNOTATIONS_PACKAGE + "/TypeEncoding;";
    public static final String SELECTOR = "org.robovm.objc.Selector";
    public static final String NATIVE_OBJECT = "org.robovm.rt.bro.NativeObject";
    public static final String OBJC_SUPER = "org.robovm.objc.ObjCSuper";
    public static final String OBJC_CLASS = "org.robovm.objc.ObjCClass";
    public static final String OBJC_OBJECT = "org.robovm.objc.ObjCObject";
    public static final String OBJC_RUNTIME = "org.robovm.objc.ObjCRuntime";
    public static final String OBJC_EXTENSIONS = "org.robovm.objc.ObjCExtensions";
    public static final String NS_OBJECT = "org.robovm.apple.foundation.NSObject";
    public static final String NS_OBJECT$MARSHALER = "org.robovm.apple.foundation.NSObject$Marshaler";
    public static final String NS_STRING$AS_STRING_MARSHALER = "org.robovm.apple.foundation.NSString$AsStringMarshaler";
    public static final String $M = "org.robovm.objc.$M";
    public static final String UI_EVENT = "org.robovm.apple.uikit.UIEvent";
    public static final String UI_APPLICATION_DELEGATE = "org.robovm.apple.uikit.UIApplicationDelegate";
    public static final String NS_ARRAY = "org.robovm.apple.foundation.NSArray";

    private boolean initialized = false;
    private Config config;
    private SootClass org_robovm_rt_bro_NativeObject = null;
    private SootClass org_robovm_objc_ObjCClass = null;
    private SootClass org_robovm_objc_ObjCSuper = null;
    private SootClass org_robovm_objc_ObjCObject = null;
    private SootClass org_robovm_objc_ObjCRuntime = null;
    private SootClass org_robovm_objc_ObjCExtensions = null;
    private SootClass org_robovm_objc_Selector = null;
    private SootClass java_lang_String = null;
    private SootClass java_lang_Class = null;
    private SootClass org_robovm_apple_foundation_NSObject = null;
    private SootClass org_robovm_objc_$M = null;
    private SootClass org_robovm_apple_uikit_UIEvent = null;
    private SootClass org_robovm_apple_uikit_UIApplicationDelegate = null;
    private SootClass org_robovm_apple_foundation_NSArray = null;
    private SootClass org_robovm_apple_foundation_NSObject$Marshaler = null;
    private SootClass org_robovm_apple_foundation_NSString$AsStringMarshaler = null;
    private SootMethodRef org_robovm_objc_ObjCObject_getSuper = null;
    private SootFieldRef org_robovm_objc_ObjCObject_customClass = null;
    private SootMethodRef org_robovm_objc_ObjCClass_getByType = null;
    private SootMethodRef org_robovm_objc_ObjCClass_associateAlias = null;
    private SootMethodRef org_robovm_objc_ObjCRuntime_bind = null;
    private SootMethodRef org_robovm_objc_ObjCObject_updateStrongRef = null;
    private SootMethodRef org_robovm_objc_ObjCExtensions_updateStrongRef = null;
    private SootMethodRef org_robovm_objc_Selector_register = null;
    private SootMethodRef org_robovm_objc_ObjCObject_getPeerObject = null;
    private SootMethodRef org_robovm_objc_ObjCObject_retainCustomObjectFromCb = null;
    private SootMethodRef org_robovm_rt_bro_NativeObject_setHandle = null;
    private SootMethodRef org_robovm_rt_bro_NativeObject_getHandle = null;
    private SootMethodRef org_robovm_apple_foundation_NSObject_forceSkipInit = null;
    private SootClass java_lang_NoSuchMethodError = null;
    private SootMethodRef java_lang_NoSuchMethodError_init = null;

    static SootMethod getOrCreateStaticInitializer(SootClass sootClass) {
        for (SootMethod m : sootClass.getMethods()) {
            if ("".equals(m.getName())) {
                return m;
            }
        }
        SootMethod m = new SootMethod("", Collections.emptyList(), VoidType.v(), STATIC);
        Body body = Jimple.v().newBody(m);
        body.getUnits().add(Jimple.v().newReturnVoidStmt());
        m.setActiveBody(body);
        sootClass.addMethod(m);
        return m;
    }

    private String getSelectorFieldName(String selectorName) {
        return "$sel$" + selectorName.replace(':', '$');
    }

    private SootField getSelectorField(String selectorName) {
        return new SootField(getSelectorFieldName(selectorName),
                org_robovm_objc_Selector.getType(), STATIC | PRIVATE | FINAL);
    }

    @SuppressWarnings("unchecked")
    private SootMethod getMsgSendMethod(String selectorName, SootMethod method, SootMethod annotatedMethod, boolean isCallback,
            Type receiverType, boolean extensions) {

        List paramTypes = new ArrayList<>();
        if (extensions) {
            paramTypes.add(method.getParameterType(0));
        } else if (method.isStatic()) {
            paramTypes.add(org_robovm_objc_ObjCClass.getType());
        } else {
            paramTypes.add(receiverType == null ? method.getDeclaringClass().getType() : receiverType);
        }
        paramTypes.add(org_robovm_objc_Selector.getType());
        if (extensions) {
            paramTypes.addAll(method.getParameterTypes().subList(1, method.getParameterTypes().size()));
        } else {
            paramTypes.addAll(method.getParameterTypes());
        }
        SootMethod m = new SootMethod((isCallback ? "$cb$" : "$m$") + selectorName.replace(':', '$'),
                paramTypes, method.getReturnType(), STATIC | PRIVATE | (isCallback ? 0 : NATIVE));
        copyAnnotations(annotatedMethod, m, extensions);

        createGenericSignatureForMsgSend(annotatedMethod, m, paramTypes, extensions);

        return m;
    }

    @SuppressWarnings("unchecked")
    private SootMethod getMsgSendInitMethod(String selectorName, SootMethod method) {

        List paramTypes = new ArrayList<>();
        paramTypes.add(LongType.v());
        paramTypes.add(org_robovm_objc_Selector.getType());
        paramTypes.addAll(method.getParameterTypes());
        SootMethod m = new SootMethod("$cb$" + selectorName.replace(':', '$'),
                paramTypes, LongType.v(), STATIC | PRIVATE);
        copyAnnotations(method, m, false);
        Annotations.addRuntimeVisibleParameterAnnotation(m, 0, Annotations.POINTER);
        Annotations.addRuntimeVisibleAnnotation(m, Annotations.POINTER);
        createGenericSignatureForMsgSend(method, m, paramTypes, false);

        return m;
    }

    private void createGenericSignatureForMsgSend(SootMethod annotatedMethod, SootMethod m, List paramTypes, boolean extensions) {
        SignatureTag tag = (SignatureTag) annotatedMethod.getTag(SignatureTag.class.getSimpleName());
        if (tag != null) {
            SootMethodType type = new SootMethodType(annotatedMethod);
            List genericParameterTypes = new ArrayList<>();
            for (org.robovm.compiler.util.generic.Type t : type.getGenericParameterTypes()) {
                genericParameterTypes.add(t.toGenericSignature());
            }
            if (!extensions) {
                /*
                 * For non extensions the generic signature is missing the
                 * receiver as parameter 1.
                 */
                genericParameterTypes.add(0, Types.getDescriptor(paramTypes.get(0)));
            }
            /*
             * Always insert the selector as parameter 2 of the generic
             * signature.
             */
            genericParameterTypes.add(1, Types.getDescriptor(paramTypes.get(1)));

            StringBuilder sb = new StringBuilder("(");
            sb.append(StringUtils.join(genericParameterTypes, ""));
            sb.append(")");
            sb.append(type.getGenericReturnType().toGenericSignature());
            m.addTag(new SignatureTag(sb.toString()));
        }
    }

    private static void copyAnnotations(SootMethod fromMethod, SootMethod toMethod, boolean extensions) {
        Annotations.copyAnnotations(fromMethod, toMethod, Visibility.Any);
        if (extensions) {
            copyParameterAnnotations(fromMethod, toMethod, 0, 1, 0, Visibility.Any);
            if (fromMethod.getParameterCount() > 1) {
                copyParameterAnnotations(fromMethod, toMethod, 1, fromMethod.getParameterCount(), 1, Visibility.Any);
            }
        } else {
            copyParameterAnnotations(fromMethod, toMethod, 0, fromMethod.getParameterCount(), 2, Visibility.Any);
        }
    }

    private SootMethod getMsgSendMethod(String selectorName, SootMethod method, boolean extensions) {
        return getMsgSendMethod(selectorName, method, method, false, null, extensions);
    }

    @SuppressWarnings("unchecked")
    private SootMethod getMsgSendSuperMethod(String selectorName, SootMethod method) {
        List paramTypes = new ArrayList<>();
        paramTypes.add(org_robovm_objc_ObjCSuper.getType());
        paramTypes.add(org_robovm_objc_Selector.getType());
        paramTypes.addAll(method.getParameterTypes());
        SootMethod m = new SootMethod("$m$super$" + selectorName.replace(':', '$'),
                paramTypes, method.getReturnType(), STATIC | PRIVATE | NATIVE);
        copyAnnotations(method, m, false);
        createGenericSignatureForMsgSend(method, m, paramTypes, false);
        return m;
    }

    private SootMethod getCallbackMethod(String selectorName, SootMethod method, SootMethod annotatedMethod, Type receiverType) {
        return getMsgSendMethod(selectorName, method, annotatedMethod, true, receiverType, false);
    }

    private void addBindCall(SootClass sootClass) {
        Jimple j = Jimple.v();

        SootMethod clinit = getOrCreateStaticInitializer(sootClass);
        Body body = clinit.retrieveActiveBody();

        String internalName = sootClass.getName().replace('.', '/');
        ClassConstant c = ClassConstant.v(internalName);

        Chain units = body.getUnits();

        // Don't call bind if there's already a call in the static initializer
        for (Unit unit : units) {
            if (unit instanceof InvokeStmt) {
                InvokeStmt stmt = (InvokeStmt) unit;
                if (stmt.getInvokeExpr() instanceof StaticInvokeExpr) {
                    StaticInvokeExpr expr = (StaticInvokeExpr) stmt.getInvokeExpr();
                    SootMethodRef ref = expr.getMethodRef();
                    if (ref.isStatic() && ref.declaringClass().equals(org_robovm_objc_ObjCRuntime)
                            && ref.name().equals("bind")) {
                        if (ref.parameterTypes().isEmpty() || expr.getArg(0).equals(c)) {
                            return;
                        }
                    }
                }
            }
        }

        // Call ObjCRuntime.bind()
        units.insertBefore(
                j.newInvokeStmt(
                        j.newStaticInvokeExpr(
                                org_robovm_objc_ObjCRuntime_bind,
                                ClassConstant.v(internalName))),
                units.getLast()
                );
    }

    private void addObjCClassField(SootClass sootClass, boolean exportClass) {
        Jimple j = Jimple.v();

        SootMethod clinit = getOrCreateStaticInitializer(sootClass);
        Body body = clinit.retrieveActiveBody();

        Local objCClass = Jimple.v().newLocal("$objCClass", org_robovm_objc_ObjCClass.getType());
        body.getLocals().add(objCClass);

        Chain units = body.getUnits();

        SootField f = new SootField("$objCClass", org_robovm_objc_ObjCClass.getType(),
                STATIC | PRIVATE | FINAL);
        sootClass.addField(f);

        units.insertBefore(
                Arrays.asList(
                        j.newAssignStmt(
                                objCClass,
                                j.newStaticInvokeExpr(
                                        org_robovm_objc_ObjCClass_getByType,
                                        ClassConstant.v(sootClass.getName().replace('.', '/')))),
                        j.newAssignStmt(
                                j.newStaticFieldRef(f.makeRef()),
                                objCClass)
                ),
                units.getLast()
        );
        if (exportClass) {
            // register and copy OBJC_CLASS_$_${CustomClassName} global variable, this will allow such classes be visible
            // from native code
            // $ $objCClass.handle and pass it to registration
            SootMethod exposeObjcClassMethod = createPublishObjcClassMethod(sootClass);
            Local objCClassHandle = Jimple.v().newLocal("$objCClassHandle", LongType.v());
            body.getLocals().add(objCClassHandle);
            units.insertBefore(
                    Arrays.asList(
                            j.newAssignStmt(objCClassHandle, j.newSpecialInvokeExpr(objCClass, this.org_robovm_rt_bro_NativeObject_getHandle)),
                            j.newAssignStmt(objCClassHandle, j.newStaticInvokeExpr(exposeObjcClassMethod.makeRef(), objCClassHandle)),
                            j.newInvokeStmt(j.newSpecialInvokeExpr(objCClass, org_robovm_objc_ObjCClass_associateAlias, objCClassHandle))
                    ),
                    units.getLast()
            );
        }
    }

    private void registerSelectors(SootClass sootClass, Set selectors) {
        Jimple j = Jimple.v();

        SootMethod clinit = getOrCreateStaticInitializer(sootClass);
        Body body = clinit.retrieveActiveBody();

        Local sel = Jimple.v().newLocal("$sel", org_robovm_objc_Selector.getType());
        body.getLocals().add(sel);

        Chain units = body.getUnits();

        for (String selectorName : selectors) {
            SootField f = getSelectorField(selectorName);
            sootClass.addField(f);

            units.insertBefore(
                    Arrays.asList(
                            j.newAssignStmt(
                                    sel,
                                    j.newStaticInvokeExpr(
                                            org_robovm_objc_Selector_register,
                                            StringConstant.v(selectorName))),
                            j.newAssignStmt(
                                    j.newStaticFieldRef(f.makeRef()),
                                    sel)
                            ),
                    units.getLast()
                    );
        }
    }

    private void init(Config config) {
        if (initialized) {
            return;
        }
        this.config = config;
        if (config.getClazzes().load(OBJC_OBJECT.replace('.', '/')) == null) {
            initialized = true;
            return;
        }
        SootResolver r = SootResolver.v();
        // These have to be resolved to HIERARCHY so that isPhantom() works
        // properly
        org_robovm_objc_ObjCObject = r.resolveClass(OBJC_OBJECT, SootClass.HIERARCHY);
        org_robovm_objc_ObjCExtensions = r.resolveClass(OBJC_EXTENSIONS, SootClass.HIERARCHY);
        // These only have to be DANGLING
        org_robovm_rt_bro_NativeObject = r.makeClassRef(NATIVE_OBJECT);
        org_robovm_objc_ObjCClass = r.makeClassRef(OBJC_CLASS);
        org_robovm_objc_ObjCSuper = r.makeClassRef(OBJC_SUPER);
        org_robovm_objc_ObjCRuntime = r.makeClassRef(OBJC_RUNTIME);
        org_robovm_objc_Selector = r.makeClassRef(SELECTOR);
        org_robovm_apple_foundation_NSObject = r.makeClassRef(NS_OBJECT);
        org_robovm_apple_foundation_NSObject$Marshaler = r.makeClassRef(NS_OBJECT$MARSHALER);
        org_robovm_apple_foundation_NSString$AsStringMarshaler = r.makeClassRef(NS_STRING$AS_STRING_MARSHALER);
        org_robovm_objc_$M = r.makeClassRef($M);
        org_robovm_apple_uikit_UIEvent = r.makeClassRef(UI_EVENT);
        org_robovm_apple_uikit_UIApplicationDelegate = r.makeClassRef(UI_APPLICATION_DELEGATE);
        org_robovm_apple_foundation_NSArray = r.makeClassRef(NS_ARRAY);
        SootClass java_lang_Object = r.makeClassRef("java.lang.Object");
        java_lang_String = r.makeClassRef("java.lang.String");
        java_lang_Class = r.makeClassRef("java.lang.Class");

        org_robovm_objc_Selector_register =
                Scene.v().makeMethodRef(
                        org_robovm_objc_Selector,
                        "register",
                        Arrays.asList(java_lang_String.getType()),
                        org_robovm_objc_Selector.getType(), true);
        org_robovm_objc_ObjCObject_getSuper =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCObject,
                        "getSuper",
                        Collections.emptyList(),
                        org_robovm_objc_ObjCSuper.getType(), false);
        org_robovm_objc_ObjCObject_updateStrongRef =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCObject,
                        "updateStrongRef",
                        Arrays.asList(
                                java_lang_Object.getType(),
                                java_lang_Object.getType()),
                        VoidType.v(), false);
        org_robovm_objc_ObjCClass_getByType =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCClass,
                        "getByType",
                        Arrays.asList(java_lang_Class.getType()),
                        org_robovm_objc_ObjCClass.getType(), true);
        org_robovm_objc_ObjCClass_associateAlias =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCClass,
                        "associateAlias",
                        Arrays.asList(LongType.v()),
                        VoidType.v(), false);
        org_robovm_objc_ObjCRuntime_bind =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCRuntime,
                        "bind",
                        Arrays.asList(java_lang_Class.getType()),
                        VoidType.v(), true);
        org_robovm_objc_ObjCObject_customClass =
                Scene.v().makeFieldRef(
                        org_robovm_objc_ObjCObject,
                        "customClass", BooleanType.v(), false);
        org_robovm_objc_ObjCExtensions_updateStrongRef =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCExtensions,
                        "updateStrongRef",
                        Arrays.asList(
                                org_robovm_objc_ObjCObject.getType(),
                                java_lang_Object.getType(),
                                java_lang_Object.getType()),
                        VoidType.v(), true);
        org_robovm_objc_ObjCObject_getPeerObject =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCObject,
                        "getPeerObject",
                        Arrays.asList(LongType.v()),
                        this.org_robovm_objc_ObjCObject.getType(), true);
        org_robovm_objc_ObjCObject_retainCustomObjectFromCb =
                Scene.v().makeMethodRef(
                        org_robovm_objc_ObjCObject,
                        "retainCustomObjectFromCb",
                        Arrays.asList(LongType.v()),
                        VoidType.v(), true);
        org_robovm_rt_bro_NativeObject_setHandle =
                Scene.v().makeMethodRef(
                        org_robovm_rt_bro_NativeObject,
                        "setHandle",
                        Arrays.asList(
                                LongType.v()),
                        VoidType.v(), false);
        org_robovm_rt_bro_NativeObject_getHandle =
                Scene.v().makeMethodRef(
                        org_robovm_rt_bro_NativeObject,
                        "getHandle",
                        Collections.emptyList(),
                        LongType.v(), false);
        org_robovm_apple_foundation_NSObject_forceSkipInit =
                Scene.v().makeMethodRef(
                        org_robovm_apple_foundation_NSObject,
                        "forceSkipInit",
                        Collections.emptyList(),
                        VoidType.v(), false);
        java_lang_NoSuchMethodError = r.makeClassRef("java.lang.NoSuchMethodError");
        java_lang_NoSuchMethodError_init =
                Scene.v().makeMethodRef(
                        java_lang_NoSuchMethodError,
                        "",
                        Arrays.asList(java_lang_String.getType()),
                        VoidType.v(), false);
        initialized = true;
    }

    private boolean isAssignable(SootClass cls, SootClass type) {
        if (type == null || type.isPhantom()) {
            return false;
        }

        if (type.isInterface()) {
            // check if cls implement interfaces
            while (cls != null) {
                for (SootClass inf : cls.getInterfaces()) {
                    if (inf == type)
                        return true;
                }
                cls = cls.hasSuperclass() ? cls.getSuperclass() : null;
            }
            return false;
        } else {
            while (cls != type && cls.hasSuperclass()) {
                cls = cls.getSuperclass();
            }
            return cls == type;
        }
    }

    private boolean isObjCObject(SootClass cls) {
        return isAssignable(cls, org_robovm_objc_ObjCObject);
    }

    private boolean isObjCExtensions(SootClass cls) {
        return isAssignable(cls, org_robovm_objc_ObjCExtensions);
    }

    private boolean isNSObject(Type type) {
        return (type instanceof RefType)
                && isAssignable(((RefType) type).getSootClass(), org_robovm_apple_foundation_NSObject);
    }

    private boolean isUIEvent(Type type) {
        return (type instanceof RefType)
                && isAssignable(((RefType) type).getSootClass(), org_robovm_apple_uikit_UIEvent);
    }

    private boolean isUIApplicationDelegate(Type type) {
        return (type instanceof RefType)
                && isAssignable(((RefType) type).getSootClass(), org_robovm_apple_uikit_UIApplicationDelegate);
    }

    private boolean isSelector(Type type) {
        return (type instanceof RefType)
                && isAssignable(((RefType) type).getSootClass(), org_robovm_objc_Selector);
    }

    private boolean isNSArray(Type type) {
        return (type instanceof RefType)
                && isAssignable(((RefType) type).getSootClass(), org_robovm_apple_foundation_NSArray);
    }

    private boolean isNSObject$Marshaler_toNative(SootMethod method) {
        return method.getDeclaringClass().getType().equals(org_robovm_apple_foundation_NSObject$Marshaler.getType())
                && method.getName().equals("toNative") && method.getParameterCount() == 2
                && method.getParameterType(0).equals(org_robovm_apple_foundation_NSObject.getType())
                && method.getParameterType(1).equals(LongType.v())
                && method.getReturnType().equals(LongType.v());
    }

    private boolean isNSObject$Marshaler_toObject(SootMethod method) {
        return method.getDeclaringClass().getType().equals(org_robovm_apple_foundation_NSObject$Marshaler.getType())
                && method.getName().equals("toObject") && method.getParameterCount() == 3
                && method.getParameterType(0).equals(java_lang_Class.getType())
                && method.getParameterType(1).equals(LongType.v())
                && method.getParameterType(2).equals(LongType.v())
                && method.getReturnType().equals(org_robovm_apple_foundation_NSObject.getType());
    }

    private boolean isNSString$AsStringMarshaler_toNative(SootMethod method) {
        return method.getDeclaringClass().getType().equals(org_robovm_apple_foundation_NSString$AsStringMarshaler.getType())
                && method.getName().equals("toNative") && method.getParameterCount() == 2
                && method.getParameterType(0).equals(java_lang_String.getType())
                && method.getParameterType(1).equals(LongType.v())
                && method.getReturnType().equals(LongType.v());
    }

    private boolean isNSString$AsStringMarshaler_toObject(SootMethod method) {
        return method.getDeclaringClass().getType().equals(org_robovm_apple_foundation_NSString$AsStringMarshaler.getType())
                && method.getName().equals("toObject") && method.getParameterCount() == 3
                && method.getParameterType(0).equals(java_lang_Class.getType())
                && method.getParameterType(1).equals(LongType.v())
                && method.getParameterType(2).equals(LongType.v())
                && method.getReturnType().equals(java_lang_String.getType());
    }

    private boolean isCustomClass(SootClass cls) {
        return !hasAnnotation(cls, NATIVE_CLASS) && !hasAnnotation(cls, NATIVE_PROTOCOL_PROXY) && isNSObject(cls.getType());
    }

    private static String getCustomClassName(SootClass cls) {
        AnnotationTag annotation = getAnnotation(cls, CUSTOM_CLASS);
        String name = annotation != null ? readStringElem(annotation, "value", "").trim() : null;
        if (name == null || name.isEmpty())
            name = "j_" + cls.getName().replace('.', '_');
        return name;
    }

    @Override
    public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) {
        init(config);
        SootClass sootClass = clazz.getSootClass();
        boolean extensions = false;
        boolean customClass = hasAnnotation(sootClass, CUSTOM_CLASS);
        boolean uiAppDelegate = !customClass && isUIApplicationDelegate(sootClass.getType());
        if (!sootClass.isInterface()
                && (isObjCObject(sootClass) || (extensions = isObjCExtensions(sootClass)))) {

            if (customClass) {
                // create a setter methods for fields marked with IBOutlet/Collection
                // once created following transformMethod run will create callbacks for them
                transformIBOutletFieldToSetters(sootClass);
            }

            Set selectors = new TreeSet<>();
            Set overridables = new HashSet<>();
            Set initializers = new HashSet<>();
            for (SootMethod method : sootClass.getMethods()) {
                if (!"".equals(method.getName()) && !"".equals(method.getName())) {
                    transformMethod(config, clazz, sootClass, method, selectors, overridables, extensions);
                } else if ((customClass || uiAppDelegate) && "".equals(method.getName())) {
                    transformConstructor(sootClass, method, initializers);
                }
            }

            if (customClass || uiAppDelegate) {
                transformParentConstructors(sootClass, initializers);
            }

            addBindCall(sootClass);
            if (!extensions) {
                addObjCClassField(sootClass, customClass);
            }
            registerSelectors(sootClass, selectors);
        }
    }

    @Override
    public void beforeLinker(Config config, Linker linker, Set classes) {
        preloadClassesForFramework(config, linker, classes);
        preloadObjCClassHosts(config, linker, classes);
    }

    private static  List l(E head, List tail) {
        LinkedList l = new LinkedList<>(tail);
        l.addFirst(head);
        return l;
    }

    private boolean isOverridable(SootMethod method) {
        if (method.isStatic() || method.isPrivate()
                || (method.getModifiers() & Modifier.FINAL) != 0
                || (method.getDeclaringClass().getModifiers() & Modifier.FINAL) != 0) {
            return false;
        }
        return true;
    }

    private boolean checkOverridable(Set overridables, String selectorName, SootMethod method) {
        boolean b = isOverridable(method);
        if (b && overridables.contains(selectorName)) {
            throw new CompilerException("Found multiple overridable @Method or @Property "
                    + "methods in " + method.getDeclaringClass() + " with the selector '"
                    + selectorName + "'.");
        }
        return b;
    }


    private void transformConstructor(SootClass sootClass, SootMethod constructor, Set initializers) {
        initializers.add(constructor.getSubSignature());

        AnnotationTag annotation = getAnnotation(constructor, METHOD);
        if (annotation == null) {
            SootMethod superConstructor = findOverridenMethodWithAnnotation(sootClass, constructor, METHOD);
            if (superConstructor != null)
                annotation = getAnnotation(superConstructor, METHOD);
        }

        if (annotation == null) {
            // this constructor doesn't have annotation attached
            return;
        }

        // Determine the selector
        String selectorName = readStringElem(annotation, "selector", "").trim();
        if (selectorName.length() == 0) {
            // TODO: warning here
            return;
        }


        //
        createConstructorCallback(sootClass, constructor, selectorName);
    }

    private void transformParentConstructors(SootClass sootClass, Set initializers) {
        // get default constructor
        SootMethod defaultConstructor;
        try {
            defaultConstructor = sootClass.getMethod("", Collections.emptyList(), VoidType.v());
        } catch (RuntimeException e) {
            // Soot throws RuntimeException if method not found
            defaultConstructor = null;
        }

        // move through subclasses
        SootClass supercls = sootClass.getSuperclass();
        while (!supercls.getType().equals(this.org_robovm_objc_ObjCObject.getType())) {
            for (SootMethod method : supercls.getMethods()) {
                 if (!"".equals(method.getName()))
                     continue;

                // check if such constructor already been processed
                if (initializers.contains(method.getSubSignature()))
                    continue;
                initializers.add(method.getSubSignature());

                // check if there is native method information attached
                AnnotationTag annotation = getAnnotation(method, METHOD);
                if (annotation == null) {
                    SootMethod superConstructor = findOverridenMethodWithAnnotation(supercls, method, METHOD);
                    if (superConstructor != null)
                        annotation = getAnnotation(superConstructor, METHOD);
                }

                // there is no method annotation attached which means there is no way to create callback
                if (annotation == null)
                    continue;

                // Determine the selector
                String selectorName = readStringElem(annotation, "selector", "").trim();
                if (selectorName.length() == 0) {
                    // TODO: warning here
                    continue;
                }

                // convert constructor
                if (defaultConstructor != null)
                    createParentConstructorCallback(sootClass, defaultConstructor, method, selectorName);
                else
                    createParentConstructorExceptionCallback(sootClass, method, selectorName);
            }

            supercls = supercls.getSuperclass();
        }
    }


    private void createConstructorCallback(SootClass sootClass, SootMethod constructor, String selectorName) {
        Jimple jimple = Jimple.v();

        SootMethod callbackMethod = this.getMsgSendInitMethod(selectorName, constructor);
        sootClass.addMethod(callbackMethod);

        addCallbackAnnotation(callbackMethod);
        addBindSelectorAnnotation(callbackMethod, selectorName);

        JimpleBody jimpleBody = jimple.newBody(callbackMethod);
        callbackMethod.setActiveBody(jimpleBody);
        Body body = callbackMethod.getActiveBody();
        PatchingChain units = body.getUnits();

        // pick parameters first using small hack
        jimpleBody.insertIdentityStmts();
        ArrayList args = new ArrayList<>(jimpleBody.getLocals());

        Local self = jimple.newLocal("$self", LongType.v());
        body.getLocals().add(self);
        units.add(jimple.newIdentityStmt(self, jimple.newParameterRef(LongType.v(), 0)));


        Local thiz = jimple.newLocal("$this", sootClass.getType());
        body.getLocals().add(thiz);

        NopStmt noClassCreatedAnchor = jimple.newNopStmt();
        NopStmt classAlreadyCreated = jimple.newNopStmt();
        Local peer = jimple.newLocal("$peer", this.org_robovm_objc_ObjCObject.getType());
        body.getLocals().add(peer);

        units.add(jimple.newAssignStmt(peer, jimple.newStaticInvokeExpr(this.org_robovm_objc_ObjCObject_getPeerObject, self)));
        units.add(jimple.newAssignStmt(thiz, jimple.newCastExpr(peer, thiz.getType())));
        units.add(jimple.newIfStmt(jimple.newEqExpr(thiz, NullConstant.v()), noClassCreatedAnchor));
        units.add(classAlreadyCreated);
        units.add(jimple.newReturnStmt(self));

        // lets create another instance of class
        units.add(noClassCreatedAnchor);
        units.add(jimple.newAssignStmt(thiz, jimple.newNewExpr(sootClass.getType())));

        // setting handle before init, this will allow NSObject to not alloc another native part
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, this.org_robovm_rt_bro_NativeObject_setHandle, self)));

        // constructor goes
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, constructor.makeRef(), args.subList(2, args.size()))));

        // call after marshaled to retain native part
        SootMethod afterMarshaled = findMethod(sootClass, "afterMarshaled", Arrays.asList(IntType.v()), VoidType.v(), true);
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, afterMarshaled.makeRef(), IntConstant.v(0))));

        // notify objc object to keep reference to this java side
        units.add(jimple.newInvokeStmt(jimple.newStaticInvokeExpr(this.org_robovm_objc_ObjCObject_retainCustomObjectFromCb, self)));

        // get back handle as it can be mutated as result of init
        units.add(jimple.newAssignStmt(self, jimple.newSpecialInvokeExpr(thiz, this.org_robovm_rt_bro_NativeObject_getHandle)));
        units.add(jimple.newReturnStmt(self));
    }

    private void createParentConstructorCallback(SootClass sootClass, SootMethod defaultConstructor, SootMethod parentConstructor, String selectorName) {
        // init method is required as it is used initialize native instance
        SootMethod initMethod = findMethod(sootClass, "init", parentConstructor.getParameterTypes(), LongType.v(), true);
        if (initMethod == null) {
            createParentConstructorExceptionCallback(sootClass, parentConstructor, selectorName);
            return;
        }

        Jimple jimple = Jimple.v();

        SootMethod callbackMethod = this.getMsgSendInitMethod(selectorName, parentConstructor);
        sootClass.addMethod(callbackMethod);

        addCallbackAnnotation(callbackMethod);
        addBindSelectorAnnotation(callbackMethod, selectorName);

        JimpleBody jimpleBody = jimple.newBody(callbackMethod);
        callbackMethod.setActiveBody(jimpleBody);
        Body body = callbackMethod.getActiveBody();
        PatchingChain units = body.getUnits();

        // pick parameters first using small hack
        jimpleBody.insertIdentityStmts();
        ArrayList args = new ArrayList<>(jimpleBody.getLocals());

        Local self = jimple.newLocal("$self", LongType.v());
        body.getLocals().add(self);
        units.add(jimple.newIdentityStmt(self, jimple.newParameterRef(LongType.v(), 0)));
        Local thiz = jimple.newLocal("$this", sootClass.getType());
        body.getLocals().add(thiz);

        NopStmt noClassCreatedAnchor = jimple.newNopStmt();
        NopStmt classAlreadyCreated = jimple.newNopStmt();
        Local peer = jimple.newLocal("$peer", this.org_robovm_objc_ObjCObject.getType());
        body.getLocals().add(peer);

        units.add(jimple.newAssignStmt(peer, jimple.newStaticInvokeExpr(this.org_robovm_objc_ObjCObject_getPeerObject, self)));
        units.add(jimple.newAssignStmt(thiz, jimple.newCastExpr(peer, thiz.getType())));
        units.add(jimple.newIfStmt(jimple.newEqExpr(thiz, NullConstant.v()), noClassCreatedAnchor));
        units.add(classAlreadyCreated);
        units.add(jimple.newReturnStmt(self));

        // lets create another instance of class
        units.add(noClassCreatedAnchor);
        units.add(jimple.newAssignStmt(thiz, jimple.newNewExpr(sootClass.getType())));

        // setting handle before init, this will allow NSObject to not alloc another native part
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, this.org_robovm_rt_bro_NativeObject_setHandle, self)));

        // tell default constructor not to call default init
        if (isNSObject(sootClass.getType()))
            units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, this.org_robovm_apple_foundation_NSObject_forceSkipInit)));

        // calling default constructor
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, defaultConstructor.makeRef(), Collections.emptyList())));

        // now call the init method to initialize native part
        units.add(jimple.newAssignStmt(self, jimple.newSpecialInvokeExpr(thiz, initMethod.makeRef(), args.subList(2, args.size()))));

        // associate java object with native handle
        SootMethod initObject = findMethod(sootClass, "initObject", Arrays.asList(LongType.v()), VoidType.v(), true);
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, initObject.makeRef(), self)));

        // call after marshaled to retain native part
        SootMethod afterMarshaled = findMethod(sootClass, "afterMarshaled", Arrays.asList(IntType.v()), VoidType.v(), true);
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(thiz, afterMarshaled.makeRef(), IntConstant.v(0))));

        // notify objc object to keep reference to this java side
        units.add(jimple.newInvokeStmt(jimple.newStaticInvokeExpr(this.org_robovm_objc_ObjCObject_retainCustomObjectFromCb, self)));

        // get back handle as it can be mutated as result of init
        units.add(jimple.newAssignStmt(self, jimple.newSpecialInvokeExpr(thiz, this.org_robovm_rt_bro_NativeObject_getHandle)));
        units.add(jimple.newReturnStmt(self));
    }

    private void createParentConstructorExceptionCallback(SootClass sootClass, SootMethod constructor, String selectorName) {
        Jimple jimple = Jimple.v();

        SootMethod callbackMethod = this.getMsgSendInitMethod(selectorName, constructor);
        sootClass.addMethod(callbackMethod);

        addCallbackAnnotation(callbackMethod);
        addBindSelectorAnnotation(callbackMethod, selectorName);

        callbackMethod.setActiveBody(jimple.newBody(callbackMethod));
        Body body = callbackMethod.getActiveBody();
        PatchingChain units = body.getUnits();

        // callback defined as following , pick self there
        Local self = jimple.newLocal("$self", LongType.v());
        body.getLocals().add(self);
        units.add(jimple.newIdentityStmt(self, jimple.newParameterRef(LongType.v(), 0)));

        Local thiz = jimple.newLocal("$this", sootClass.getType());
        body.getLocals().add(thiz);

        Local peer = jimple.newLocal("$peer", this.org_robovm_objc_ObjCObject.getType());
        body.getLocals().add(peer);
        units.add(jimple.newAssignStmt(peer, jimple.newStaticInvokeExpr(this.org_robovm_objc_ObjCObject_getPeerObject, self)));
        units.add(jimple.newAssignStmt(thiz, jimple.newCastExpr(peer, thiz.getType())));

        // check if pointer already associated with object
        // this is not expected but possible due mix of java/native classes and receiving call from
        // native code as super
        NopStmt noClassCreatedAnchor = jimple.newNopStmt();
        NopStmt classAlreadyCreatedAnchor = jimple.newNopStmt();
        units.add(jimple.newIfStmt(jimple.newEqExpr(thiz, NullConstant.v()), noClassCreatedAnchor));
        units.add(classAlreadyCreatedAnchor);
        units.add(jimple.newReturnStmt(self));

        // there is no java part, throw exception
        units.add(noClassCreatedAnchor);
        String msg = String.format("Objctive-C called -%s which could not be mapped to a constructor in %s. Expected a default constructor or a %s constructor.",
                selectorName, sootClass.getName(), constructor.getSubSignature());

        Local exc = jimple.newLocal("$exc", java_lang_NoSuchMethodError.getType());
        body.getLocals().add(exc);
        units.add(jimple.newAssignStmt(exc, jimple.newNewExpr(java_lang_NoSuchMethodError.getType())));
        units.add(jimple.newInvokeStmt(jimple.newSpecialInvokeExpr(exc, java_lang_NoSuchMethodError_init, StringConstant.v(msg))));
        units.add(jimple.newThrowStmt(exc));
    }

    private void transformIBOutletFieldToSetters(SootClass sootClass) {
        // find out all fields marked with annotation
        for (SootField field : sootClass.getFields()) {
            AnnotationTag annotation = getAnnotation(field, IBOUTLETCOLLECTION);
            if (annotation != null) {
                if (!isNSArray(field.getType())) {
                    throw new CompilerException("Objective-C @IBOutletCollection field " + field +
                            " must be of type NSArray.");
                }
            } else {
                annotation = getAnnotation(field, IBOUTLET);
                if (annotation == null)
                    annotation = getAnnotation(field, IBINSPECTABLE);
            }

            // skip fields without annotations
            if (annotation == null)
                continue;

            // validate against static/final
            if (field.isStatic()) {
                throw new CompilerException("Objective-C @IBOutlet/@IBOutletCollection/@IBInspectable field " + field + " must not be static.");
            }
            if (field.isFinal()) {
                throw new CompilerException("Objective-C @IBOutlet/@IBOutletCollection/@IBInspectable field " + field + " must not be final.");
            }

            // create a setter method
            Jimple jimple = Jimple.v();
            SootMethod method = new SootMethod("$field$set_" + field.getName(), Collections.singletonList(field.getType()),
                    VoidType.v(), PRIVATE | FINAL);
            // if field is struct -- add @ByVal annotation to it
            if (Types.isStruct(field.getType()))
                Annotations.addRuntimeVisibleParameterAnnotation(method, 0, Annotations.BY_VAL);
            sootClass.addMethod(method);

            method.setActiveBody(jimple.newBody(method));
            Body body = method.getActiveBody();
            PatchingChain units = body.getUnits();
            // synth simple setter method like:
            // @IBOutlet(selector = "setSomething:")
            // void setSomething(T v) { this.something = v; }
            Local thiz = jimple.newLocal("$this", sootClass.getType());
            Local value = jimple.newLocal("$value", field.getType());
            body.getLocals().add(thiz);
            body.getLocals().add(value);
            units.add(jimple.newIdentityStmt(thiz, jimple.newThisRef(sootClass.getType())));
            units.add(jimple.newIdentityStmt(value, jimple.newParameterRef(field.getType(), 0)));
            units.add(jimple.newAssignStmt(jimple.newInstanceFieldRef(thiz, field.makeRef()), value));
            units.add(jimple.newReturnVoidStmt());

            // now attach annotation to allow transformMethod to recognize this method
            String propertyName = Annotations.readStringElem(annotation, "name", field.getName());
            String setterSelectorName = "set" + StringUtils.capitalize(propertyName) + ":";
            annotation = new AnnotationTag(annotation.getType(), 1);
            // need to attach selector param as field and property could be different
            annotation.addElem(new AnnotationStringElem(setterSelectorName, 's', "selector"));
            Annotations.addRuntimeVisibleAnnotation(method, annotation);
        }
    }

    private void transformMethod(Config config, Clazz clazz, SootClass sootClass,
            SootMethod method, Set selectors, Set overridables, boolean extensions) {

        AnnotationTag annotation = getAnnotation(method, METHOD);
        if (annotation != null) {
            if (extensions && !(method.isStatic() && method.isNative())) {
                throw new CompilerException("Objective-C @Method method "
                        + method + " in extension class must be static and native.");
            }

            transformObjCMethod(annotation, sootClass, method, selectors, overridables, extensions);
            return;
        }

        annotation = getAnnotation(method, IBACTION);
        if (annotation != null) {
            if (method.isStatic() || method.isNative()) {
                throw new CompilerException("Objective-C @IBAction method "
                        + method + " must not be static or native.");
            }
            int paramCount = method.getParameterCount();
            Type param1 = paramCount > 0 ? method.getParameterType(0) : null;
            Type param2 = paramCount > 1 ? method.getParameterType(1) : null;
            if (method.getReturnType() != VoidType.v() || paramCount > 2
                    || (param1 != null && (!isNSObject(param1) && !isNSObject(param1)))
                    || (param2 != null && (!isUIEvent(param2) || isNSObject(param1)))) {
                throw new CompilerException("Objective-C @IBAction method "
                        + method + " does not have a supported signature. @IBAction methods"
                        + " must return void and either take no arguments, 1 argument of type NSObject"
                        + ", or 2 arguments of types NSObject and UIEvent.");
            }

            transformObjCMethod(annotation, sootClass, method, selectors, overridables, extensions);
            return;
        }

        annotation = getAnnotation(method, PROPERTY);
        if (annotation != null) {
            if (extensions && !(method.isStatic() && method.isNative())) {
                throw new CompilerException("Objective-C @Property method "
                        + method + " in extension class must be static and native.");
            }

            transformObjCProperty(annotation, "@Property", sootClass, method, selectors, overridables, extensions);
            return;
        }
        annotation = getAnnotation(method, IBOUTLET);
        if (annotation != null) {
            if (method.isStatic()) {
                throw new CompilerException("Objective-C @IBOutlet method "
                        + method + " must not be static.");
            }

            transformObjCProperty(annotation, "@IBOutlet", sootClass, method, selectors, overridables, extensions);
            return;
        }
        annotation = getAnnotation(method, IBINSPECTABLE);
        if (annotation != null) {
            if (method.isStatic()) {
                throw new CompilerException("Objective-C @IBInspectable method "
                        + method + " must not be static.");
            }

            transformObjCProperty(annotation, "@IBInspectable", sootClass, method, selectors, overridables, extensions);
            return;
        }
        annotation = getAnnotation(method, IBOUTLETCOLLECTION);
        if (annotation != null) {
            if (method.isStatic()) {
                throw new CompilerException("Objective-C @IBOutletCollection method "
                        + method + " must not be static.");
            }
            if (method.getReturnType() != VoidType.v() && !isNSArray(method.getReturnType())
                    || method.getReturnType() == VoidType.v() && method.getParameterCount() == 1 && !isNSArray(method
                            .getParameterType(0))) {
                throw new CompilerException("Objective-C @IBOutletCollection method " + method
                        + " does not have a supported signature. "
                        + "@IBOutletCollection getter methods must return NSArray. "
                        + "@IBOutletCollection setter methods must have 1 parameter of type NSArray.");
            }

            transformObjCProperty(annotation, "@IBOutletCollection", sootClass, method, selectors, overridables,
                    extensions);
            return;
        }

        if (!method.isStatic() && !method.isNative() && !method.isAbstract() && !method.isPrivate()
                && isCustomClass(sootClass) && !hasAnnotation(method, NOT_IMPLEMENTED)) {
            /*
             * Create a @Callback for this method if it overrides a
             * @Method/@Property method in a superclass/interface.
             */
            List superMethods = findOverriddenMethods(sootClass, method);
            for (SootMethod superMethod : superMethods) {
                if (createCustomClassCallbackIfNeeded(sootClass, method, superMethod)) {
                    break;
                }
            }
        }
    }

    private boolean createCustomClassCallbackIfNeeded(SootClass sootClass, SootMethod method, SootMethod superMethod) {
        AnnotationTag annotation = getAnnotation(superMethod, METHOD);
        if (annotation != null) {
            createCallback(sootClass, method, superMethod, getObjCMethodSelectorName(annotation, superMethod, false),
                    getReceiverType(sootClass));
            return true;
        } else {
            annotation = getAnnotation(superMethod, PROPERTY);
            if (annotation != null) {
                boolean isGetter = method.getReturnType() != VoidType.v();
                createCallback(sootClass, method, superMethod, getObjCPropertySelectorName(annotation, superMethod, isGetter),
                        getReceiverType(sootClass));
                return true;
            }
        }
        return false;
    }

    private SootMethod findMethod(SootClass sootClass, String methodName, List parameterTypes, Type returnType, boolean includeObjC) {
        boolean done;
        do {
            try {
                SootMethod m = sootClass.getMethod(methodName, parameterTypes, returnType);
                return m;
            } catch (RuntimeException e) {
                // Soot throws RuntimeException if method not found
            }

            // break if processed ObjC already
            done = sootClass.getType().equals(org_robovm_objc_ObjCObject.getType());
            sootClass = sootClass.getSuperclass();
            // break if now is ObjC and it is not included
            done |= !includeObjC && sootClass.getType().equals(org_robovm_objc_ObjCObject.getType());
        } while (!done);

        return null;
    }

    private List findOverriddenMethods(SootClass sootClass, SootMethod method) {
        SootClass supercls = sootClass.getSuperclass();
        while (!supercls.getType().equals(org_robovm_objc_ObjCObject.getType())) {
            try {
                SootMethod m = supercls.getMethod(method.getName(), method.getParameterTypes(),
                        method.getReturnType());
                if (overrides(method, m) && !hasAnnotation(m, NOT_IMPLEMENTED)) {
                    return Collections.singletonList(m);
                }
            } catch (RuntimeException e) {
                // Soot throws RuntimeException if method not found
            }
            supercls = supercls.getSuperclass();
        }

        /*
         * Method doesn't override any method in superclasses. Check interfaces
         * as well. There may be several methods in interfaces which this method
         * overrides. We have to return all of them as we cannot assume that
         * first one found has the @Method or @Property annotation.
         */
        List candidates = new ArrayList<>();
        findOverriddenMethodsOnInterfaces(sootClass, method, candidates);

        return candidates;
    }

    private void findOverriddenMethodsOnInterfaces(SootClass sootClass, SootMethod method, List candidates) {
        if (sootClass.isInterface()) {
            try {
                candidates.add(sootClass.getMethod(method.getName(), method.getParameterTypes(),
                        method.getReturnType()));
            } catch (RuntimeException e) {
                // Soot throws RuntimeException if method not found
            }
        }

        for (SootClass interfaze : sootClass.getInterfaces()) {
            findOverriddenMethodsOnInterfaces(interfaze, method, candidates);
        }
        if (!sootClass.isInterface() && sootClass.hasSuperclass()
                && !sootClass.getSuperclass().getType().equals(org_robovm_objc_ObjCObject.getType())) {
            findOverriddenMethodsOnInterfaces(sootClass.getSuperclass(), method, candidates);
        }
    }

    private SootMethod findOverridenMethodWithAnnotation(SootClass sootClass, SootMethod method, String annotationPath) {
        SootClass supercls = sootClass.getSuperclass();
        while (!supercls.getType().equals(org_robovm_objc_ObjCObject.getType())) {
            try {
                SootMethod m = supercls.getMethod(method.getName(), method.getParameterTypes(),
                        method.getReturnType());
                AnnotationTag annotation = getAnnotation(m, annotationPath);
                if (annotation != null)
                    return m;
            } catch (RuntimeException e) {
                // Soot throws RuntimeException if method not found
            }
            supercls = supercls.getSuperclass();
        }

        return null;
    }

    /**
     * Returns {@code true} if subMethod overrides superMethod. The signatures
     * are assumed to be the same when this is called.
     */
    private boolean overrides(SootMethod subMethod, SootMethod superMethod) {
        if (!superMethod.isPrivate() && !superMethod.isStatic()) {
            if (!superMethod.isPublic() && !superMethod.isProtected()) {
                /*
                 * Package private. The class of the methods must be in the same
                 * package.
                 */
                String package1 = superMethod.getDeclaringClass().getPackageName();
                String package2 = subMethod.getDeclaringClass().getPackageName();
                return package1.equals(package2);
            }

            /*
             * superMethod is public or protected. subMethod must be overriding
             * superMethod or else it would violate the JVM spec since an
             * overriding method must not have a more restrictive visibility
             * than the method it overrides.
             */
            return true;
        }
        return false;
    }

    private void transformObjCMethod(AnnotationTag annotation, SootClass sootClass,
            SootMethod method, Set selectors, Set overridables, boolean extensions) {

        // Determine the selector
        String selectorName = getObjCMethodSelectorName(annotation, method, extensions);

        // Create the @Bridge and @Callback methods needed for this selector
        if (!extensions && isCustomClass(sootClass)) {
            createCallback(sootClass, method, method, selectorName, getReceiverType(sootClass));
        }
        if (method.isNative()) {
            if (checkOverridable(overridables, selectorName, method)) {
                overridables.add(selectorName);
            }
            selectors.add(selectorName);
            createBridge(sootClass, method, selectorName, false, extensions);
        }
    }

    private Type getReceiverType(SootClass sootClass) {
        Type receiverType = ObjCProtocolProxyPlugin.isObjCProxy(sootClass)
                ? sootClass.getInterfaces().getFirst().getType()
                : sootClass.getType();
        return receiverType;
    }

    private void transformObjCProperty(AnnotationTag annotation, String javaAnnotation, SootClass sootClass,
            SootMethod method, Set selectors, Set overridables, boolean extensions) {

        int getterParamCount = extensions ? 1 : 0;
        int setterParamCount = extensions ? 2 : 1;
        if (method.getReturnType() != VoidType.v() && method.getParameterCount() != getterParamCount
                || method.getReturnType() == VoidType.v() && method.getParameterCount() != setterParamCount) {

            if (!extensions) {
                throw new CompilerException("Objective-C " + getAnnotationName(annotation) + " method " + method
                        + " does not have a supported signature. " + getAnnotationName(annotation) + " getter methods"
                        + " must take 0 arguments and must not return void. " + getAnnotationName(annotation)
                        + " setter methods must take 1 argument and return void.");
            }
            throw new CompilerException("Objective-C " + getAnnotationName(annotation) + " method " + method
                    + " in extension class does not have a supported signature. " + getAnnotationName(annotation)
                    + " getter methods in extension classes" + " must take 1 argument (the 'this' reference) and "
                    + "must not return void. " + getAnnotationName(annotation) + " setter methods in extension classes "
                    + "must take 2 arguments (first is the 'this' reference) and return void.");
        }

        boolean isGetter = method.getReturnType() != VoidType.v();

        // Determine the selector
        String selectorName = getObjCPropertySelectorName(annotation, method, isGetter);

        // Create the @Bridge and @Callback methods needed for this selector
        if (!extensions && isCustomClass(sootClass)) {
            createCallback(sootClass, method, method, selectorName, getReceiverType(sootClass));
        }
        if (method.isNative()) {
            if (checkOverridable(overridables, selectorName, method)) {
                overridables.add(selectorName);
            }
            selectors.add(selectorName);
            boolean strongRefSetter = !isGetter && readBooleanElem(annotation, "strongRef", false);
            createBridge(sootClass, method, selectorName, strongRefSetter, extensions);
        }
    }

    private String getAnnotationName(AnnotationTag annotation) {
        // annotation.getType() returns the descriptor Lcom/example/ClassName;
        String n = annotation.getType();
        n = n.substring(1, n.length() - 1);
        return "@" + n.substring(n.lastIndexOf('/') + 1);
    }

    private String getObjCMethodSelectorName(AnnotationTag annotation, SootMethod method, boolean extensions) {
        String selectorName = readStringElem(annotation, "selector", "").trim();
        if (selectorName.length() == 0) {
            int argCount = method.getParameterCount();
            if (IBACTION.equals(annotation.getType()) && argCount == 2) {
                selectorName = method.getName() + ":withEvent:";
            } else {
                StringBuilder sb = new StringBuilder(method.getName());
                for (int i = extensions ? 1 : 0; i < argCount; i++) {
                    sb.append(':');
                }
                selectorName = sb.toString();
            }
        }
        return selectorName;
    }

    private String getObjCPropertySelectorName(AnnotationTag annotation, SootMethod method,
            boolean isGetter) {
        String selectorName = readStringElem(annotation, "selector", "").trim();
        if (selectorName.length() == 0) {
            String methodName = method.getName();
            if (!(isGetter && methodName.startsWith("get") && methodName.length() > 3)
                    && !(isGetter && methodName.startsWith("is") && methodName.length() > 2)
                    && !(!isGetter && methodName.startsWith("set") && methodName.length() > 3)) {
                throw new CompilerException("Invalid Objective-C " + getAnnotationName(annotation) + " method name "
                        + method + ". " + getAnnotationName(annotation) + " methods without an explicit selector value "
                        + "must follow the Java beans property method naming convention.");
            }
            selectorName = methodName;
            if (isGetter) {
                selectorName = methodName.startsWith("is")
                        ? methodName.substring(2)
                        : methodName.substring(3);
                selectorName = selectorName.substring(0, 1).toLowerCase()
                        + selectorName.substring(1);
            } else {
                selectorName += ":";
            }
        }
        return selectorName;
    }

    private void createCallback(SootClass sootClass, SootMethod method, SootMethod annotatedMethod, String selectorName, Type receiverType) {
        Jimple j = Jimple.v();

        SootMethod callbackMethod = getCallbackMethod(selectorName, method, annotatedMethod, receiverType);
        sootClass.addMethod(callbackMethod);
        addCallbackAnnotation(callbackMethod);
        addBindSelectorAnnotation(callbackMethod, selectorName);
        if (!hasAnnotation(annotatedMethod, TYPE_ENCODING) && (isCustomClass(sootClass)
                || ObjCProtocolProxyPlugin.isObjCProxy(sootClass))) {
            String encoding = generateTypeEncoding(callbackMethod);
            try {
                addTypeEncodingAnnotation(callbackMethod, encoding);
            } catch (IllegalArgumentException e) {
                throw new CompilerException("Failed to determine method type encoding for method "
                        + method + ": " + e.getMessage());
            }
        }

        Body body = j.newBody(callbackMethod);
        callbackMethod.setActiveBody(body);
        PatchingChain units = body.getUnits();

        Local thiz = null;
        if (!method.isStatic()) {
            thiz = j.newLocal("$this", receiverType);
            body.getLocals().add(thiz);
            units.add(j.newIdentityStmt(thiz, j.newParameterRef(receiverType, 0)));
        }
        LinkedList args = new LinkedList<>();
        for (int i = 0; i < method.getParameterCount(); i++) {
            Type t = method.getParameterType(i);
            Local p = j.newLocal("$p" + i, t);
            body.getLocals().add(p);
            units.add(j.newIdentityStmt(p, j.newParameterRef(t, i + 2)));
            args.add(p);
        }

        Local ret = null;
        if (method.getReturnType() != VoidType.v()) {
            ret = j.newLocal("$ret", method.getReturnType());
            body.getLocals().add(ret);
        }

        SootMethodRef targetMethod = method.makeRef();
        if (((RefType) receiverType).getSootClass().isInterface()) {
            @SuppressWarnings("unchecked") List parameterTypes = method.getParameterTypes();
            targetMethod = Scene.v().makeMethodRef(
                    ((RefType) receiverType).getSootClass(),
                    method.getName(),
                    parameterTypes,
                    method.getReturnType(), false);
        }

        InvokeExpr expr = method.isStatic()
                ? j.newStaticInvokeExpr(targetMethod, args)
                : (((RefType) receiverType).getSootClass().isInterface()
                        ? j.newInterfaceInvokeExpr(thiz, targetMethod, args)
                        : j.newVirtualInvokeExpr(thiz, targetMethod, args));
        units.add(
                ret == null
                        ? j.newInvokeStmt(expr)
                        : j.newAssignStmt(ret, expr)
                );

        if (ret != null) {
            units.add(j.newReturnStmt(ret));
        } else {
            units.add(j.newReturnVoidStmt());
        }
    }

    private String generateTypeEncoding(SootMethod method) {
        TypeEncoder encoder = new TypeEncoder();
        return encoder.encode(method, !config.getArch().is32Bit());
    }

    private SootMethod findStrongRefGetter(SootClass sootClass,
            final SootMethod method, boolean extensions) {

        AnnotationTag annotation = getAnnotation(method, PROPERTY);
        if (annotation == null) {
            annotation = getAnnotation(method, IBOUTLET);
        }
        if (annotation == null) {
            annotation = getAnnotation(method, IBINSPECTABLE);
        }
        if (annotation == null) {
            annotation = getAnnotation(method, IBOUTLETCOLLECTION);
        }

        String setterPropName = readStringElem(annotation, "name", "").trim();
        if (setterPropName.length() == 0) {
            String methodName = method.getName();
            if (!methodName.startsWith("set") || methodName.length() == 3) {
                throw new CompilerException("Failed to determine the property "
                        + "name from the @Property method " + method
                        + ". Either specify the name explicitly in the @Property "
                        + "annotation or rename the method according to the Java "
                        + "beans property setter method naming convention.");
            }
            setterPropName = methodName.substring(3);
            setterPropName = setterPropName.substring(0, 1).toLowerCase() + setterPropName.substring(1);
        }

        int paramCount = extensions ? 1 : 0;
        Type propType = method.getParameterType(extensions ? 1 : 0);
        for (SootMethod m : sootClass.getMethods()) {
            if (m != method && method.isStatic() == m.isStatic()
                    && m.getParameterCount() == paramCount && m.getReturnType().equals(propType)) {

                AnnotationTag propertyAnno = getAnnotation(m, PROPERTY);
                if (propertyAnno != null) {
                    String getterPropName = readStringElem(propertyAnno, "name", "").trim();
                    if (getterPropName.length() == 0) {
                        String methodName = m.getName();
                        if (!methodName.startsWith("get") || methodName.length() == 3) {
                            // Not a candidate. No name set and not a Java beans
                            // style getter
                            continue;
                        }
                        getterPropName = methodName.substring(3);
                        getterPropName = getterPropName.substring(0, 1).toLowerCase() + getterPropName.substring(1);
                    }
                    if (setterPropName.equals(getterPropName)) {
                        return m;
                    }
                }
            }
        }

        throw new CompilerException("Failed to determine the getter method "
                + "corresponding to the strong ref @Property setter method " + method
                + ". The getter must either specify the name explicitly in the @Property "
                + "annotation or be named according to the Java "
                + "beans property getter method naming convention.");
    }

    /**
     * Takes a method returned by
     * {@link #getMsgSendMethod(String, SootMethod, boolean)} and returns a
     * {@link SootMethodRef} to it or to a matching method in the {@code $M}
     * class.
     */
    private SootMethodRef getGenericMsgSendReplacementMethod(SootMethod method) {
        if (method.getParameterCount() == 2) {
            if (isNSObject(method.getParameterType(0)) && isSelector(method.getParameterType(1))
                    && !hasAnnotation(method, MARSHALER)) {
                /*
                 * This is a no args ObjC method (only takes self and a
                 * selector). If it doesn't use any special marshaler for self
                 * and it returns a primitive, an NSObject using the default
                 * marshaler or a String marshaled from an NSString we can
                 * replace it with a call to $M.
                 */
                MarshalerMethod param0MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                        new MarshalSite(method, 0));
                if (isNSObject$Marshaler_toNative(param0MarshalerMethod.getMethod())) {
                    // self uses the default marshaler
                    List paramTypes = Arrays.asList(org_robovm_apple_foundation_NSObject.getType(),
                            org_robovm_objc_Selector.getType());
                    if (method.getReturnType() == VoidType.v() || method.getReturnType() instanceof PrimType) {
                        // Primitive return type or void
                        String prefix = getPrimitiveReturnTypeModifier(method);
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, prefix + "_objc_msgSend",
                                paramTypes, method.getReturnType(), true);
                    } else if (isNSObject(method.getReturnType())) {
                        MarshalerMethod retMarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                                new MarshalSite(method));
                        if (isNSObject$Marshaler_toObject(retMarshalerMethod.getMethod())) {
                            // Returns NSObject using the default marshaler
                            return Scene.v().makeMethodRef(org_robovm_objc_$M, "object_objc_msgSend",
                                    paramTypes, org_robovm_apple_foundation_NSObject.getType(), true);
                        }
                    } else if (method.getReturnType().equals(java_lang_String.getType())) {
                        MarshalerMethod retMarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                                new MarshalSite(method));
                        if (isNSString$AsStringMarshaler_toObject(retMarshalerMethod.getMethod())) {
                            // Returns String marshaled using
                            // NSSring$AsStringMarshaler
                            return Scene.v().makeMethodRef(org_robovm_objc_$M, "string_objc_msgSend",
                                    paramTypes, method.getReturnType(), true);
                        }
                    }
                }
            }
        } else if (method.getParameterCount() == 3 && method.getReturnType() == VoidType.v()) {
            if (isNSObject(method.getParameterType(0)) && isSelector(method.getParameterType(1))
                    && !hasParameterAnnotation(method, 1, MARSHALER)) {
                /*
                 * This is a 1 arg ObjC method with no return type (void) (takes
                 * self, a selector and a third arg). If it doesn't use any
                 * special marshaler for self and the third arg is a primitive,
                 * an NSObject using the default marshaler or a String marshaled
                 * from an NSString we can replace it with a call to $M.
                 */
                MarshalerMethod param0MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                        new MarshalSite(method, 0));
                if (isNSObject$Marshaler_toNative(param0MarshalerMethod.getMethod())) {
                    // self uses the default marshaler
                    List paramTypes = Arrays.asList(org_robovm_apple_foundation_NSObject.getType(),
                            org_robovm_objc_Selector.getType(), method.getParameterType(2));
                    if (method.getParameterType(2) instanceof PrimType) {
                        // Arg is a primitive
                        String suffix = getPrimitiveParameterTypeModifier(method, 2);
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSend_" + suffix,
                                paramTypes, method.getReturnType(), true);
                    } else if (isNSObject(method.getParameterType(2))) {
                        MarshalerMethod param2MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                                new MarshalSite(method, 2));
                        if (isNSObject$Marshaler_toNative(param2MarshalerMethod.getMethod())) {
                            // Arg is an NSObject using the default marshaler
                            paramTypes = Arrays.asList(org_robovm_apple_foundation_NSObject.getType(),
                                    org_robovm_objc_Selector.getType(), org_robovm_apple_foundation_NSObject.getType());
                            return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSend_object",
                                    paramTypes, method.getReturnType(), true);
                        }
                    } else if (method.getParameterType(2).equals(java_lang_String.getType())) {
                        MarshalerMethod param2MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                                new MarshalSite(method, 2));
                        if (isNSString$AsStringMarshaler_toNative(param2MarshalerMethod.getMethod())) {
                            // Arg is a String marshaled using
                            // NSSring@AsStringMarshaler
                            return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSend_string",
                                    paramTypes, method.getReturnType(), true);
                        }
                    }
                }
            }
        }
        return method.makeRef();
    }

    /**
     * Takes a method returned by
     * {@link #getMsgSendSuperMethod(String, SootMethod)} and returns a
     * {@link SootMethodRef} to it or to a matching method in the {@code $M}
     * class.
     */
    private SootMethodRef getGenericMsgSendSuperReplacementMethod(SootMethod method) {
        if (method.getParameterCount() == 2) {
            /*
             * This is a no args ObjC method (only takes super and a selector).
             * If it returns a primitive, an NSObject using the default
             * marshaler or a String marshaled from an NSString we can replace
             * it with a call to $M.
             */
            if (method.getParameterType(0).equals(org_robovm_objc_ObjCSuper.getType())
                    && isSelector(method.getParameterType(1)) && !hasAnnotation(method, MARSHALER)) {
                if (method.getReturnType() == VoidType.v() || method.getReturnType() instanceof PrimType) {
                    // Primitive return type or void
                    String prefix = getPrimitiveReturnTypeModifier(method);
                    return Scene.v().makeMethodRef(org_robovm_objc_$M, prefix + "_objc_msgSendSuper",
                            method.getParameterTypes(), method.getReturnType(), true);
                } else if (isNSObject(method.getReturnType())) {
                    MarshalerMethod retMarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                            new MarshalSite(method));
                    if (isNSObject$Marshaler_toObject(retMarshalerMethod.getMethod())) {
                        // Returns NSObject using the default marshaler
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, "object_objc_msgSendSuper",
                                method.getParameterTypes(), org_robovm_apple_foundation_NSObject.getType(), true);
                    }
                } else if (method.getReturnType().equals(java_lang_String.getType())) {
                    MarshalerMethod retMarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                            new MarshalSite(method));
                    if (isNSString$AsStringMarshaler_toObject(retMarshalerMethod.getMethod())) {
                        // Returns String marshaled using
                        // NSSring$AsStringMarshaler
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, "string_objc_msgSendSuper",
                                method.getParameterTypes(), method.getReturnType(), true);
                    }
                }
            }
        } else if (method.getParameterCount() == 3 && method.getReturnType() == VoidType.v()) {
            if (method.getParameterType(0).equals(org_robovm_objc_ObjCSuper.getType())
                    && isSelector(method.getParameterType(1)) && !hasParameterAnnotation(method, 1, MARSHALER)) {
                /*
                 * This is a 1 arg ObjC method with no return type (void) (takes
                 * super, a selector and a third arg). If the third arg is a
                 * primitive, an NSObject using the default marshaler or a
                 * String marshaled from an NSString we can replace it with a
                 * call to $M.
                 */
                if (method.getParameterType(2) instanceof PrimType) {
                    // Arg is a primitive
                    String suffix = getPrimitiveParameterTypeModifier(method, 2);
                    return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSendSuper_" + suffix,
                            method.getParameterTypes(), method.getReturnType(), true);
                } else if (isNSObject(method.getParameterType(2))) {
                    MarshalerMethod param2MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                            new MarshalSite(method, 2));
                    if (isNSObject$Marshaler_toNative(param2MarshalerMethod.getMethod())) {
                        // Arg is an NSObject using the default marshaler
                        List paramTypes = Arrays.asList(org_robovm_objc_ObjCSuper.getType(),
                                org_robovm_objc_Selector.getType(), org_robovm_apple_foundation_NSObject.getType());
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSendSuper_object",
                                paramTypes, method.getReturnType(), true);
                    }
                } else if (method.getParameterType(2).equals(java_lang_String.getType())) {
                    MarshalerMethod param2MarshalerMethod = config.getMarshalerLookup().findMarshalerMethod(
                            new MarshalSite(method, 2));
                    if (isNSString$AsStringMarshaler_toNative(param2MarshalerMethod.getMethod())) {
                        // Arg is a String marshaled using
                        // NSSring@AsStringMarshaler
                        return Scene.v().makeMethodRef(org_robovm_objc_$M, "objc_msgSendSuper_string",
                                method.getParameterTypes(), method.getReturnType(), true);
                    }
                }
            }
        }
        return method.makeRef();
    }

    private String getPrimitiveReturnTypeModifier(SootMethod method) {
        String mod = method.getReturnType().toString();
        if (method.getReturnType() == LongType.v() && hasPointerAnnotation(method)) {
            mod = "ptr";
        } else if ((method.getReturnType() == FloatType.v() || method.getReturnType() == DoubleType.v())
                && hasMachineSizedFloatAnnotation(method)) {
            mod = "m" + mod;
        } else if (method.getReturnType() == LongType.v() && hasMachineSizedSIntAnnotation(method)) {
            mod = "msint";
        } else if (method.getReturnType() == LongType.v() && hasMachineSizedUIntAnnotation(method)) {
            mod = "muint";
        }
        return mod;
    }

    private String getPrimitiveParameterTypeModifier(SootMethod method, int paramIdx) {
        String mod = method.getParameterType(paramIdx).toString();
        if (method.getParameterType(paramIdx) == LongType.v() && hasPointerAnnotation(method, paramIdx)) {
            mod = "ptr";
        } else if ((method.getParameterType(paramIdx) == FloatType.v() || method.getParameterType(paramIdx) == DoubleType.v())
                && hasMachineSizedFloatAnnotation(method, paramIdx)) {
            mod = "m" + mod;
        } else if (method.getParameterType(paramIdx) == LongType.v() && hasMachineSizedSIntAnnotation(method, paramIdx)) {
            mod = "msint";
        } else if (method.getParameterType(paramIdx) == LongType.v() && hasMachineSizedUIntAnnotation(method, paramIdx)) {
            mod = "muint";
        }
        return mod;
    }

    private void createBridge(SootClass sootClass, SootMethod method, String selectorName,
            boolean strongRefSetter, boolean extensions) {

        Jimple j = Jimple.v();

        boolean usingGenericInstanceMethod = false;

        SootMethod msgSendMethod = getMsgSendMethod(selectorName, method, extensions);
        /*
         * Add the method even if we might remove it later to make marshaler
         * lookup on the method work as expected.
         */
        sootClass.addMethod(msgSendMethod);
        addBridgeAnnotation(msgSendMethod);
        SootMethodRef msgSendMethodRef = getGenericMsgSendReplacementMethod(msgSendMethod);
        if (!msgSendMethodRef.declaringClass().getType().equals(msgSendMethod.getDeclaringClass().getType())) {
            /*
             * There's a generic objc_msgSend method we can use. Remove
             * msgSendMethod from the class.
             */
            sootClass.removeMethod(msgSendMethod);

            /*
             * Can we use a generic _instance method from $M? If we can we
             * won't have to make a call to objc_msgSendSuper.
             */
            if (!method.isStatic()) {
                // Yes!
                msgSendMethodRef = Scene.v().makeMethodRef(msgSendMethodRef.declaringClass(),
                        msgSendMethodRef.name() + "_instance", msgSendMethodRef.parameterTypes(),
                        msgSendMethodRef.returnType(), true);
                usingGenericInstanceMethod = true;
            }
        }

        SootMethodRef msgSendSuperMethodRef = null;
        if (!usingGenericInstanceMethod && !extensions && !method.isStatic()) {
            SootMethod msgSendSuperMethod = getMsgSendSuperMethod(selectorName, method);
            /*
             * Add the method even if we might remove it later to make marshaler
             * lookup on the method work as expected.
             */
            sootClass.addMethod(msgSendSuperMethod);
            addBridgeAnnotation(msgSendSuperMethod);
            msgSendSuperMethodRef = getGenericMsgSendSuperReplacementMethod(msgSendSuperMethod);
            if (!msgSendSuperMethodRef.declaringClass().getType().equals(msgSendSuperMethod.getDeclaringClass().getType())) {
                /*
                 * There's a generic objc_msgSendSuper method we can use. Remove
                 * msgSendSuperMethod from the class.
                 */
                sootClass.removeMethod(msgSendSuperMethod);
            }
        }

        method.setModifiers(method.getModifiers() & ~NATIVE);

        Body body = j.newBody(method);
        method.setActiveBody(body);
        PatchingChain units = body.getUnits();
        Local thiz = null;
        if (extensions) {
            thiz = j.newLocal("$this", method.getParameterType(0));
            body.getLocals().add(thiz);
            units.add(j.newIdentityStmt(thiz, j.newParameterRef(method.getParameterType(0), 0)));
        } else if (!method.isStatic()) {
            thiz = j.newLocal("$this", sootClass.getType());
            body.getLocals().add(thiz);
            units.add(j.newIdentityStmt(thiz, j.newThisRef(sootClass.getType())));
        }
        LinkedList args = new LinkedList<>();
        for (int i = extensions ? 1 : 0; i < method.getParameterCount(); i++) {
            Type t = method.getParameterType(i);
            Local p = j.newLocal("$p" + i, t);
            body.getLocals().add(p);
            units.add(j.newIdentityStmt(p, j.newParameterRef(t, i)));
            args.add(p);
        }

        Local objCClass = null;
        if (!extensions && method.isStatic()) {
            objCClass = j.newLocal("$objCClass", org_robovm_objc_ObjCClass.getType());
            body.getLocals().add(objCClass);
            units.add(
                    j.newAssignStmt(
                            objCClass,
                            j.newStaticFieldRef(
                                    Scene.v().makeFieldRef(
                                            sootClass,
                                            "$objCClass", org_robovm_objc_ObjCClass.getType(), true))
                            )
                    );
        }

        if (strongRefSetter) {
            Type propType = method.getParameterType(extensions ? 1 : 0);
            if (propType instanceof RefLikeType) {
                SootMethodRef getter = findStrongRefGetter(sootClass, method, extensions).makeRef();
                Local before = j.newLocal("$before", propType);
                body.getLocals().add(before);
                units.add(
                        j.newAssignStmt(
                                before,
                                extensions
                                        ? j.newStaticInvokeExpr(getter, thiz)
                                        : (objCClass != null
                                                ? j.newStaticInvokeExpr(getter)
                                                : j.newVirtualInvokeExpr(thiz, getter))
                                )
                        );
                Value after = args.get(0);
                if (extensions) {
                    units.add(
                            j.newInvokeStmt(
                                    j.newStaticInvokeExpr(
                                            org_robovm_objc_ObjCExtensions_updateStrongRef,
                                            Arrays.asList(thiz, before, after)))
                            );
                } else {
                    units.add(
                            j.newInvokeStmt(
                                    j.newVirtualInvokeExpr(
                                            objCClass != null ? objCClass : thiz,
                                            org_robovm_objc_ObjCObject_updateStrongRef,
                                            before, after))
                            );
                }
            }
        }

        Local sel = j.newLocal("$sel", org_robovm_objc_Selector.getType());
        body.getLocals().add(sel);
        // $sel = 
        units.add(
                j.newAssignStmt(
                        sel,
                        j.newStaticFieldRef(
                                Scene.v().makeFieldRef(
                                        sootClass,
                                        getSelectorFieldName(selectorName),
                                        org_robovm_objc_Selector.getType(), true)))
                );
        args.addFirst(sel);

        Local customClass = null;
        if (!usingGenericInstanceMethod && !extensions && !Modifier.isFinal(sootClass.getModifiers()) && !method.isStatic()) {
            customClass = j.newLocal("$customClass", BooleanType.v());
            body.getLocals().add(customClass);
            units.add(
                    j.newAssignStmt(
                            customClass,
                            j.newInstanceFieldRef(
                                    thiz,
                                    org_robovm_objc_ObjCObject_customClass)
                            )
                    );
        }

        Local ret = null;
        if (method.getReturnType() != VoidType.v()) {
            ret = j.newLocal("$ret", msgSendMethodRef.returnType());
            body.getLocals().add(ret);
        }
        Local castRet = null;
        if (!msgSendMethodRef.returnType().equals(method.getReturnType())) {
            /*
             * We're calling a generic method in $M which returns an NSObject.
             * We need to cast that to the return type declared by the method
             * being generated.
             */
            castRet = j.newLocal("$castRet", method.getReturnType());
            body.getLocals().add(castRet);
        }

        StaticInvokeExpr invokeMsgSendExpr =
                j.newStaticInvokeExpr(
                        msgSendMethodRef,
                        l(thiz != null ? thiz : objCClass, args));
        Stmt invokeMsgSendStmt = ret == null
                ? j.newInvokeStmt(invokeMsgSendExpr)
                : j.newAssignStmt(ret, invokeMsgSendExpr);

        if (customClass != null) {
            // if $customClass == 0 goto 
            units.add(
                    j.newIfStmt(
                            j.newEqExpr(customClass, IntConstant.v(0)),
                            invokeMsgSendStmt)
                    );

            // $super = this.getSuper()
            Local zuper = j.newLocal("$super", org_robovm_objc_ObjCSuper.getType());
            body.getLocals().add(zuper);
            units.add(
                    j.newAssignStmt(
                            zuper,
                            j.newVirtualInvokeExpr(
                                    body.getThisLocal(),
                                    org_robovm_objc_ObjCObject_getSuper))
                    );
            StaticInvokeExpr invokeMsgSendSuperExpr =
                    j.newStaticInvokeExpr(
                            msgSendSuperMethodRef,
                            l(zuper, args));
            units.add(
                    ret == null
                            ? j.newInvokeStmt(invokeMsgSendSuperExpr)
                            : j.newAssignStmt(ret, invokeMsgSendSuperExpr)
                    );
            if (ret != null) {
                if (castRet != null) {
                    units.add(j.newAssignStmt(castRet, j.newCastExpr(ret, castRet.getType())));
                    units.add(j.newReturnStmt(castRet));
                } else {
                    units.add(j.newReturnStmt(ret));
                }
            } else {
                units.add(j.newReturnVoidStmt());
            }
        }

        units.add(invokeMsgSendStmt);
        if (ret != null) {
            if (castRet != null) {
                units.add(j.newAssignStmt(castRet, j.newCastExpr(ret, castRet.getType())));
                units.add(j.newReturnStmt(castRet));
            } else {
                units.add(j.newReturnStmt(ret));
            }
        } else {
            units.add(j.newReturnVoidStmt());
        }
    }

    static void addBridgeAnnotation(SootMethod method) {
        addRuntimeVisibleAnnotation(method, BRIDGE);
    }

    static void addCallbackAnnotation(SootMethod method) {
        addRuntimeVisibleAnnotation(method, CALLBACK);
    }

    static void addBindSelectorAnnotation(SootMethod method, String selectorName) {
        AnnotationTag annotationTag = new AnnotationTag(BIND_SELECTOR, 1);
        annotationTag.addElem(new AnnotationStringElem(selectorName, 's', "value"));
        addRuntimeVisibleAnnotation(method, annotationTag);
    }

    static void addNotImplementedAnnotation(SootMethod method, String selectorName) {
        AnnotationTag annotationTag = new AnnotationTag(NOT_IMPLEMENTED, 1);
        annotationTag.addElem(new AnnotationStringElem(selectorName, 's', "value"));
        addRuntimeVisibleAnnotation(method, annotationTag);
    }

    static void addTypeEncodingAnnotation(SootMethod method, String encoding) {
        AnnotationTag annotationTag = new AnnotationTag(TYPE_ENCODING, 1);
        annotationTag.addElem(new AnnotationStringElem(encoding, 's', "value"));
        addRuntimeVisibleAnnotation(method, annotationTag);
    }

    static SootMethod createPublishObjcClassMethod(SootClass sootClass) {
        // empty method to copy data from $objCClass.getHandle to OBJC_CLASS_$_ClassName
        // long $publishObjClass(long handle)
        SootMethod m = new SootMethod("$publishObjClass", Collections.singletonList(LongType.v()), LongType.v(), STATIC | PRIVATE );
        Body body = Jimple.v().newBody(m);
        body.getUnits().add(Jimple.v().newReturnStmt(LongConstant.v(0)));
        m.setActiveBody(body);
        sootClass.addMethod(m);
        return m;
    }

    private void preloadClassesForFramework(Config config, Linker linker, Set classes) {
        if (FrameworkTarget.matches(config.getTargetType())) {
            // for framework target it is required to make list of @CustomClasses to be preloaded
            // once framework is loaded
            // generate byte array of zero terminated string for frameworksupport.m class
            StringBuilder sb = new StringBuilder();
            for (Clazz clazz : classes) {
                if (!hasAnnotation(clazz.getSootClass(), CUSTOM_CLASS))
                    continue;
                sb.append(clazz.getInternalName()).append('\0');
            }
            if (sb.length() != 0) {
                byte[] data = sb.append('\0').toString().getBytes();
                linker.addBcGlobalData("_bcFrameworkPreloadClasses", data);
            }

            // TODO: probably ObjMemberPlugin is not best place for this logic
            // but Targets are not integrated into build process and introducing
            // this infrastructure will require significant changes
            // disable JVM start up if JNI_CreateJavaVM is listed in exported symbols
            if (config.getExportedSymbols().contains("JNI_CreateJavaVM")) {
                byte[] data = new byte[1];
                linker.addBcGlobalData("_bcFrameworkSkipJavaVMStartup", data);
            }
        }
    }

    private void preloadObjCClassHosts(Config config, Linker linker, Set classes) {
        // TODO: remove once resolved on Idea side
        // affects only debug builds
        // workaround for Idea Bug: https://youtrack.jetbrains.com/issue/IDEA-332794
        // Idea debugger is not happy if inner class is being loaded before host ones and
        // ignores breakpoints inside.
        // preload if all ObjCClass's is happening in ObjCClass.java, static initializer
        // workaround -- is to preload all hosts before loading ObjCClass
        if (config.isDebug()) {
            SootClass objCObjectClazz = classes.stream()
                    .filter( c -> c.getClassName().equals(OBJC_OBJECT))
                    .map(Clazz::getSootClass)
                    .findFirst()
                    .orElse(null);
            Set objClassHosts = new HashSet<>();
            if (objCObjectClazz != null) {
                // proceed if we link with ObjObject
                // look for all ObjCClasses, and these are internal -- preload hosts as well
                for (Clazz clazz : classes) {
                    SootClass sootClass = clazz.getSootClass();
                    // skip if doesn't extend ObjCClass
                    if (!SootClassUtils.isAssignableFrom(sootClass, objCObjectClazz))
                        continue;
                    // skip if not inner
                    String hostClassName = SootClassUtils.getEnclosingClassName(sootClass);
                    if (hostClassName == null)
                        hostClassName = SootClassUtils.getDeclaringClassName(sootClass);
                    if (hostClassName != null)
                        objClassHosts.add(hostClassName);
                }

                if (!objClassHosts.isEmpty()) {
                    try {
                        byte[] bytes = StringUtils.join(objClassHosts, ",").getBytes("UTF8");
                        linker.addRuntimeData(OBJC_CLASS + ".preloadClasses", bytes);
                    } catch (UnsupportedEncodingException e) {
                        config.getLogger().error("Failed to prepare ObjCClass' hosts preload list");
                    }
                }
            }
        }
    }

    public static class MethodCompiler extends AbstractMethodCompiler {
        // structure to expose CustomClasses to objc side
        // check https://opensource.apple.com/source/objc4/objc4-437/runtime/objc-runtime-new.h for details
        // typedef struct class_t {
        //    struct class_t *isa;
        //    struct class_t *superclass;
        //    Cache cache;
        //    IMP *vtable;
        //    class_rw_t *data;
        // } class_t;
        static StructureType objc_class_type = new StructureType(
                org.robovm.compiler.llvm.Type.I8_PTR,
                org.robovm.compiler.llvm.Type.I8_PTR,
                org.robovm.compiler.llvm.Type.I8_PTR,
                org.robovm.compiler.llvm.Type.I8_PTR,
                org.robovm.compiler.llvm.Type.I8_PTR);

        public MethodCompiler(Config config) {
            super(config);
        }

        public boolean willCompile(SootMethod method) {
            return "$publishObjClass".equals(method.getName()) && Annotations.hasAnnotation(method.getDeclaringClass(), ObjCMemberPlugin.CUSTOM_CLASS);
        }

        @Override
        protected Function doCompile(ModuleBuilder moduleBuilder, SootMethod method) {
            return publishObjClass(moduleBuilder, method);
        }

        private Function publishObjClass(ModuleBuilder moduleBuilder, SootMethod method) {
            Function fn = createMethodFunction(method);
            moduleBuilder.addFunction(fn);

            // synthetic method `private static void publishObjClass(long objcClassHandle)` added in bind call
            // implementing function:
            // 1. adding OBJC_CLASS_$_${CustomClassName} global to allow linking with this class
            // 2. code to memcpy class_t (objcClassHandle) to this global
            // add global OBJC_CLASS_$_${CustomClassName} , it will allow
            String objClassName = "OBJC_CLASS_$_" + getCustomClassName(method.getDeclaringClass());
            Global OBJC_CLASS = new Global(objClassName, new ZeroInitializer(objc_class_type), false);
            moduleBuilder.addGlobal(OBJC_CLASS);
            // generate memcpy(OBJC_CLASS_$_${CustomClassName}, objcClassHandle, sizeof(class_t))
            Variable dest = new Variable("dest", org.robovm.compiler.llvm.Type.I8_PTR);
            Variable src = new Variable("src", org.robovm.compiler.llvm.Type.I8_PTR);
            Variable sizeof = new Variable("sizeof", org.robovm.compiler.llvm.Type.I32);
            fn.add(new Bitcast(dest, OBJC_CLASS.ref(), dest.getType()));
            fn.add(new Inttoptr(src, fn.getParameterRef(1), dest.getType()));
            fn.add(new Ptrtoint(sizeof,
                    new ConstantGetelementptr(new org.robovm.compiler.llvm.NullConstant(new PointerType(objc_class_type)), 1),
                    org.robovm.compiler.llvm.Type.I32));
            fn.add(new Call(Functions.LLVM_MEMCPY, dest.ref(), src.ref(), sizeof.ref(), new IntegerConstant(1), BooleanConstant.TRUE));

            // return back pointer to OBJC_CLASS_$_${CustomClassName}
            Variable ret = new Variable("ret", org.robovm.compiler.llvm.Type.I64);
            fn.add(new Ptrtoint(ret, dest.ref(), ret.getType()));
            fn.add(new Ret(ret.ref()));
            return fn;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy