/* * 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 <http://www.gnu.org/licenses/gpl-2.0.html>. */ package org.robovm.compiler.plugin.objc; import static org.robovm.compiler.Annotations.*; import static soot.Modifier.*; 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 org.apache.commons.lang3.StringUtils; import org.robovm.compiler.Annotations; import org.robovm.compiler.Annotations.Visibility; import org.robovm.compiler.CompilerException; 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.plugin.AbstractCompilerPlugin; import org.robovm.compiler.plugin.CompilerPlugin; import org.robovm.compiler.util.generic.SootMethodType; import soot.Body; import soot.BooleanType; import soot.DoubleType; import soot.FloatType; 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.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; /** * {@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 IBOUTLETCOLLECTION = "L" + OBJC_ANNOTATIONS_PACKAGE + "/IBOutletCollection;"; public static final String NATIVE_CLASS = "L" + OBJC_ANNOTATIONS_PACKAGE + "/NativeClass;"; 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 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 NS_ARRAY = "org.robovm.apple.foundation.NSArray"; private boolean initialized = false; private Config config; 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_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_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; static SootMethod getOrCreateStaticInitializer(SootClass sootClass) { for (SootMethod m : sootClass.getMethods()) { if ("<clinit>".equals(m.getName())) { return m; } } SootMethod m = new SootMethod("<clinit>", 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<Type> 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; } private void createGenericSignatureForMsgSend(SootMethod annotatedMethod, SootMethod m, List<Type> paramTypes, boolean extensions) { SignatureTag tag = (SignatureTag) annotatedMethod.getTag(SignatureTag.class.getSimpleName()); if (tag != null) { SootMethodType type = new SootMethodType(annotatedMethod); List<String> 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<Type> 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<Unit> 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(<class>) units.insertBefore( j.newInvokeStmt( j.newStaticInvokeExpr( org_robovm_objc_ObjCRuntime_bind, ClassConstant.v(internalName))), units.getLast() ); } private void addObjCClassField(SootClass sootClass) { 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<Unit> units = body.getUnits(); SootField f = new SootField("$objCClass", org_robovm_objc_ObjCClass.getType(), STATIC | PRIVATE | FINAL); sootClass.addField(f); units.insertBefore( Arrays.<Unit> 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() ); } private void registerSelectors(SootClass sootClass, Set<String> 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<Unit> units = body.getUnits(); for (String selectorName : selectors) { SootField f = getSelectorField(selectorName); sootClass.addField(f); units.insertBefore( Arrays.<Unit> 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_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_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.<Type> 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.<Type> emptyList(), org_robovm_objc_ObjCSuper.getType(), false); org_robovm_objc_ObjCObject_updateStrongRef = Scene.v().makeMethodRef( org_robovm_objc_ObjCObject, "updateStrongRef", Arrays.<Type> 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.<Type> asList(java_lang_Class.getType()), org_robovm_objc_ObjCClass.getType(), true); org_robovm_objc_ObjCRuntime_bind = Scene.v().makeMethodRef( org_robovm_objc_ObjCRuntime, "bind", Arrays.<Type> 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.<Type> asList( org_robovm_objc_ObjCObject.getType(), java_lang_Object.getType(), java_lang_Object.getType()), VoidType.v(), true); initialized = true; } private boolean isAssignable(SootClass cls, SootClass type) { if (type == null || type.isPhantom()) { return false; } 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 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()); } @Override public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) { init(config); SootClass sootClass = clazz.getSootClass(); boolean extensions = false; if (!sootClass.isInterface() && (isObjCObject(sootClass) || (extensions = isObjCExtensions(sootClass)))) { Set<String> selectors = new TreeSet<>(); Set<String> overridables = new HashSet<>(); for (SootMethod method : sootClass.getMethods()) { if (!"<clinit>".equals(method.getName()) && !"<init>".equals(method.getName())) { transformMethod(config, clazz, sootClass, method, selectors, overridables, extensions); } } addBindCall(sootClass); if (!extensions) { addObjCClassField(sootClass); } registerSelectors(sootClass, selectors); } } private static <E> List<E> l(E head, List<E> tail) { LinkedList<E> 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<String> 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 transformMethod(Config config, Clazz clazz, SootClass sootClass, SootMethod method, Set<String> selectors, Set<String> 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, 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<SootMethod> 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 List<SootMethod> 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<SootMethod> candidates = new ArrayList<>(); findOverriddenMethodsOnInterfaces(sootClass, method, candidates); return candidates; } private void findOverriddenMethodsOnInterfaces(SootClass sootClass, SootMethod method, List<SootMethod> 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); } } /** * 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<String> selectors, Set<String> 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<String> selectors, Set<String> 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<Unit> 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<Value> 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<Type> 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, 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<Type> paramTypes = Arrays.<Type> 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<Type> paramTypes = Arrays.<Type> 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.<Type> 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<Type> paramTypes = Arrays.<Type> 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 <name>_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<Unit> 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<Value> 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 = <selector> 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 <invokeMsgSendStmt> 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); } }