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

org.robovm.compiler.plugin.objc.ObjCBlockPlugin 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 static org.objectweb.asm.Opcodes.*;
import static org.robovm.compiler.Annotations.*;
import static org.robovm.compiler.Types.*;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.robovm.compiler.CompilerException;
import org.robovm.compiler.ModuleBuilder;
import org.robovm.compiler.Symbols;
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.GenericArrayType;
import org.robovm.compiler.util.generic.ImplForArray;
import org.robovm.compiler.util.generic.ImplForType;
import org.robovm.compiler.util.generic.ListOfTypes;
import org.robovm.compiler.util.generic.ParameterizedType;
import org.robovm.compiler.util.generic.SootClassType;
import org.robovm.compiler.util.generic.SootMethodType;
import org.robovm.compiler.util.generic.SootTypeType;
import org.robovm.compiler.util.generic.Type;
import org.robovm.compiler.util.generic.TypeVariable;
import org.robovm.compiler.util.generic.WildcardType;

import soot.BooleanType;
import soot.ByteType;
import soot.CharType;
import soot.DoubleType;
import soot.FloatType;
import soot.IntType;
import soot.LongType;
import soot.PrimType;
import soot.RefType;
import soot.ShortType;
import soot.SootClass;
import soot.SootMethod;
import soot.SootResolver;
import soot.VoidType;
import soot.tagkit.AnnotationClassElem;
import soot.tagkit.AnnotationTag;

/**
 * {@link CompilerPlugin} which generates wrapper @Callback methods for @Block
 * annotated method parameters.
 */
public class ObjCBlockPlugin extends AbstractCompilerPlugin {
    public static final String OBJC_PACKAGE = "org/robovm/objc";
    public static final String OBJC_ANNOTATIONS_PACKAGE = OBJC_PACKAGE + "/annotation";
    public static final String BLOCK = "L" + OBJC_ANNOTATIONS_PACKAGE + "/Block;";
    public static final String TYPE_ENCODING = "L" + OBJC_ANNOTATIONS_PACKAGE + "/TypeEncoding;";
    public static final String RUNNABLE_AS_OBJC_BLOCK_MARSHALER =
            OBJC_PACKAGE + "/RunnableAsObjCBlockMarshaler";

    static Pattern BLOCK_ANNOTATION_PATTERN = Pattern.compile("@[\\w\\d_]+\\s*");
    static Map BLOCK_ANNOTATIONS = new HashMap<>();

    static {
        BLOCK_ANNOTATIONS.put("@ByVal", BY_VAL);
        BLOCK_ANNOTATIONS.put("@ByRef", BY_REF);
        BLOCK_ANNOTATIONS.put("@Pointer", POINTER);
        BLOCK_ANNOTATIONS.put("@MachineSizedFloat", MACHINE_SIZED_FLOAT);
        BLOCK_ANNOTATIONS.put("@MachineSizedSInt", MACHINE_SIZED_S_INT);
        BLOCK_ANNOTATIONS.put("@MachineSizedUInt", MACHINE_SIZED_U_INT);
        BLOCK_ANNOTATIONS.put("@Block", BLOCK);
    }
    
    private SootClass org_robovm_objc_ObjCBlock = null;
    private SootClass java_lang_Boolean = null;
    private SootClass java_lang_Byte = null;
    private SootClass java_lang_Short = null;
    private SootClass java_lang_Character = null;
    private SootClass java_lang_Integer = null;
    private SootClass java_lang_Long = null;
    private SootClass java_lang_Float = null;
    private SootClass java_lang_Double = null;

    private boolean initialized = false;
    private Config config;

    private void init() {
        if (initialized) {
            return;
        }
        SootResolver r = SootResolver.v();
        org_robovm_objc_ObjCBlock = r.makeClassRef("org.robovm.objc.ObjCBlock");
        java_lang_Boolean = r.makeClassRef("java.lang.Boolean");
        java_lang_Byte = r.makeClassRef("java.lang.Byte");
        java_lang_Short = r.makeClassRef("java.lang.Short");
        java_lang_Character = r.makeClassRef("java.lang.Character");
        java_lang_Integer = r.makeClassRef("java.lang.Integer");
        java_lang_Long = r.makeClassRef("java.lang.Long");
        java_lang_Float = r.makeClassRef("java.lang.Float");
        java_lang_Double = r.makeClassRef("java.lang.Double");
    }
    
    @Override
    public void beforeClass(Config config, Clazz clazz, ModuleBuilder moduleBuilder) throws IOException {
        this.config = config;
        init();
        SootClass sootClass = clazz.getSootClass();
        if (!sootClass.isInterface()) {
            Map blockTypeIds = new HashMap<>();
            for (SootMethod method : sootClass.getMethods()) {
                if (method.isNative() && (hasBridgeAnnotation(method) || hasGlobalValueAnnotation(method))
                    || hasCallbackAnnotation(method) || hasStructMemberAnnotation(method)) {
                    
                    int[] indexes = getBlockParameterIndexes(method);
                    if (indexes != null || hasAnnotation(method, BLOCK)) {
                        transformMethod(config, clazz, method, indexes, blockTypeIds);
                    }
                }
            }
        }
    }

    private int[] getBlockParameterIndexes(SootMethod method) {
        int[] indexes = null;
        int paramCount = method.getParameterCount();
        int idxCount = 0;
        for (int i = 0; i < paramCount; i++) {
            if (hasParameterAnnotation(method, i, BLOCK)) {
                if (indexes == null) {
                    indexes = new int[paramCount];
                    Arrays.fill(indexes, -1);
                }
                indexes[idxCount++] = i;
            }
        }
        return indexes;
    }
    
    private void transformMethod(Config config, Clazz clazz, SootMethod blockMethod, 
            int[] blockParamIndexes, Map blockTypeIds) throws IOException {
        
        SootMethodType blockMethodType = new SootMethodType(blockMethod);
        if (blockParamIndexes != null) {
            Type[] genericParameterTypes = blockMethodType.getGenericParameterTypes();
            for (int i = 0; i < blockParamIndexes.length; i++) {
                int idx = blockParamIndexes[i];
                if (idx == -1) {
                    break;
                }
                
                SootMethod targetMethod = getBlockTargetMethod(blockMethod, idx);
                Type[] actualGenericTypes = resolveTargetMethodSignature(
                        blockMethod, targetMethod, genericParameterTypes[idx]);
                soot.Type[] actualRawTypes = toRawTypes(actualGenericTypes);
                soot.Type[] unboxedTypes = unboxTypes(actualRawTypes);
                String[][] targetMethodAnnotations = 
                    parseTargetMethodAnnotations(targetMethod, 
                        readStringElem(
                            getParameterAnnotation(blockMethod, idx, BLOCK), "value", ""));
                
                // Create the marshaler class associated with this block type
                String marshaler = createBlockMarshaler(config, clazz, 
                        targetMethod, actualGenericTypes, actualRawTypes, unboxedTypes, blockTypeIds, 
                        targetMethodAnnotations);
                addMarshalerAnnotation(blockMethod, idx, marshaler);
            }
        }
        if (hasAnnotation(blockMethod, BLOCK)) {
            SootMethod targetMethod = getBlockTargetMethod(blockMethod);
            Type[] actualGenericTypes = resolveTargetMethodSignature(
                    blockMethod, targetMethod, blockMethodType.getGenericReturnType());
            soot.Type[] actualRawTypes = toRawTypes(actualGenericTypes);
            soot.Type[] unboxedTypes = unboxTypes(actualRawTypes);
            String[][] targetMethodAnnotations = 
                    parseTargetMethodAnnotations(targetMethod, 
                        readStringElem(
                            getAnnotation(blockMethod, BLOCK), "value", ""));
            String marshaler = createBlockMarshaler(config, clazz, targetMethod, 
                    actualGenericTypes, actualRawTypes, unboxedTypes, blockTypeIds, targetMethodAnnotations);
            addMarshalerAnnotation(blockMethod, marshaler);
        }
    }

    private static int parseAnnotations(SootMethod m, String originalValue, String value, TreeSet values) {
        Matcher matcher = BLOCK_ANNOTATION_PATTERN.matcher(value);
        int pos = 0;
        while (matcher.find()) {
            if (matcher.start() != pos) {
                break;
            }
            String anno = BLOCK_ANNOTATIONS.get(matcher.group().trim());
            if (anno == null) {
                throw new CompilerException("Unsupported annotation \"" 
                        + matcher.group().trim() + "\" in @Block annotation value \"" 
                        + originalValue + "\" on method " + m);
            }
            values.add(anno);
            pos = matcher.end();
        }
        return pos;
    }
    
    protected static String[][] parseTargetMethodAnnotations(SootMethod m, String value) {
        return parseTargetMethodAnnotations(m, m.getParameterCount(), value);
    }
    
    protected static String[][] parseTargetMethodAnnotations(SootMethod m, int paramCount, String value) {
        String originalValue = value;
        value = value.trim();
        String[][] result = new String[paramCount + 1][];
        if (value.length() == 0) {
            Arrays.fill(result, new String[0]);
        } else {
            TreeSet values = new TreeSet<>();
            int pos = parseAnnotations(m, originalValue, value, values);
            result[0] = new String[values.size()];
            values.toArray(result[0]);
            
            if (pos < value.length()) {
                if (value.charAt(pos) != '(') {
                    throw new CompilerException("Error in @Block annotation value \"" 
                            + originalValue + "\" on method " + m 
                            + ". Expected '(' but got '" 
                            + value.charAt(pos) + "'.");
                }
                if (pos + 1 == value.length()) {
                    throw new CompilerException("Error in @Block annotation value \"" 
                            + originalValue + "\" on method " + m 
                            + ". Expected a ')' at end of value but "
                            + "got end of string.");
                }
                value = value.substring(pos + 1).trim();
                if (value.charAt(value.length() - 1) != ')') {
                    throw new CompilerException("Error in @Block annotation value \"" 
                            + originalValue + "\" on method " + m 
                            + ". Expected a ')' at end of value but got '" 
                            + value.charAt(value.length() - 1) + "'.");
                }
                value = value.substring(0, value.length() - 1).trim();
                
                if (value.length() > 0 || paramCount > 0) {
                    String[] parts = value.split(",", paramCount + 1);
                    if (parts.length != paramCount) {
                        throw new CompilerException("Error in @Block annotation value \"" 
                                + originalValue + "\" on method " + m 
                                + ". Expected " + paramCount + " parameters");                    
                    }
                    for (int i = 0; i < parts.length; i++) {
                        String p = parts[i].trim();
                        values = new TreeSet<>();
                        pos = parseAnnotations(m, originalValue, p, values);
                        if (pos != p.length()) {
                            throw new CompilerException("Error in @Block annotation value \"" 
                                    + originalValue + "\" on method " + m 
                                    + ". Expected a ',' after parameter " 
                                    + (i + 1) + " but got '" + p.charAt(pos) + "'.");
                        }
                        result[i + 1] = new String[values.size()];
                        values.toArray(result[i + 1]);
                    }
                }
            }
        }
        return result;
    }

    static void addMarshalerAnnotation(SootMethod method, String marshalerName) {
        AnnotationTag annotationTag = new AnnotationTag(MARSHALER, 1);
        annotationTag.addElem(new AnnotationClassElem("L" + marshalerName + ";", 'c', "value"));
        addRuntimeVisibleAnnotation(method, annotationTag);
    }

    static void addMarshalerAnnotation(SootMethod method, int paramIndex, String marshalerName) {
        AnnotationTag annotationTag = new AnnotationTag(MARSHALER, 1);
        annotationTag.addElem(new AnnotationClassElem("L" + marshalerName + ";", 'c', "value"));
        addRuntimeVisibleParameterAnnotation(method, paramIndex, annotationTag);
    }

    private String getBlockMarshalerName(Clazz clazz, int id) {
        return clazz.getInternalName() + "$$BlockMarshaler" + id;
    }

    private String createBlockMarshaler(Config config, Clazz clazz,
            final SootMethod targetMethod, Type[] actualGenericTypes,
            soot.Type[] actualRawTypes, soot.Type[] unboxedTypes,
            Map blockTypeIds, String[][] targetMethodAnnotations) throws IOException {
        
        if (targetMethod.getDeclaringClass().getName().equals("java.lang.Runnable") 
                && targetMethod.getName().equals("run")) {
            return RUNNABLE_AS_OBJC_BLOCK_MARSHALER;
        }
        
        String targetMethodKey = getTargetMethodKey(targetMethod, actualRawTypes, 
                targetMethodAnnotations);
        Integer id = blockTypeIds.get(targetMethodKey);
        if (id != null) {
            // Already generated
            return getBlockMarshalerName(clazz, id);
        }
        
        id = blockTypeIds.size();
        blockTypeIds.put(targetMethodKey, id);
        
        final String blockMarshalerName = getBlockMarshalerName(clazz, id);
        final String targetInterfaceName = Types.getInternalName(targetMethod.getDeclaringClass());

        // We use RunnableAsObjCBlockMarshaler as template
        Clazz templateMarshaler = config.getClazzes().load(RUNNABLE_AS_OBJC_BLOCK_MARSHALER);
        
        final Set usedBoxMethods = new HashSet<>();
        final Set usedUnboxMethods = new HashSet<>();
        
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        generateTargetMethod(blockMarshalerName, targetMethod, actualGenericTypes, actualRawTypes, 
                unboxedTypes, usedBoxMethods, usedUnboxMethods, cw);
        generateBridgeMethod(actualGenericTypes, unboxedTypes, targetMethodAnnotations, cw);
        generateCallbackMethod(blockMarshalerName, targetMethod, actualGenericTypes, actualRawTypes, 
                unboxedTypes, usedBoxMethods, usedUnboxMethods, targetMethodAnnotations, cw);
        ClassReader classReader = new ClassReader(templateMarshaler.getBytes());
        classReader.accept(new ClassVisitor(ASM4, cw) {
            @Override
            public void visit(int version, int access, String name, String signature, String superName,
                    String[] interfaces) {
                super.visit(version, access, blockMarshalerName, signature, 
                        superName, new String[] {targetInterfaceName});
            }
            
            @Override
            public void visitInnerClass(String name, String outerName, String innerName, int access) {
                // Ignore
            }

            @Override
            public void visitSource(String source, String debug) {
                // Ignore
            }
            
            @Override
            public MethodVisitor visitMethod(int access, String name, String desc, 
                    String signature, String[] exceptions) {
                
                switch (name) {
                case "run":
                case "invoke":
                case "invoked":
                    // Skip all these
                    return null;
                case "box":
                    if (!usedBoxMethods.contains(desc)) {
                        return null;
                    }
                    break;
                case "unbox":
                    if (!usedUnboxMethods.contains(desc)) {
                        return null;
                    }
                    break;
                }

                desc = desc.replace("java/lang/Runnable", targetInterfaceName);
                signature = null;
                
                // Return a MethodVisitor which changes all occurrences of 
                // RunnableAsObjCBlockMarshaler to the blockMarshalerName
                return new MethodVisitor(ASM4, super.visitMethod(access, name, desc, signature, exceptions)) {
                    @Override
                    public void visitLdcInsn(Object cst) {
                        if (cst instanceof org.objectweb.asm.Type) {
                            if (((org.objectweb.asm.Type) cst).getSort() == org.objectweb.asm.Type.OBJECT) {
                                String internalName = ((org.objectweb.asm.Type) cst).getInternalName();
                                if (RUNNABLE_AS_OBJC_BLOCK_MARSHALER.equals(internalName)) {
                                    cst = org.objectweb.asm.Type.getObjectType(blockMarshalerName);
                                }
                            }
                        }
                        super.visitLdcInsn(cst);
                    }
                    
                    @Override
                    public void visitTypeInsn(int opcode, String type) {
                        if (RUNNABLE_AS_OBJC_BLOCK_MARSHALER.equals(type)) {
                            type = blockMarshalerName;
                        } else if ("java/lang/Runnable".equals(type)) {
                            type = targetInterfaceName;
                        }
                        super.visitTypeInsn(opcode, type);
                    }
                    
                    @Override
                    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
                        if (RUNNABLE_AS_OBJC_BLOCK_MARSHALER.equals(owner)) {
                            owner = blockMarshalerName;
                        }
                        super.visitFieldInsn(opcode, owner, name, desc);
                    }
                    
                    @Override
                    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
                        if (RUNNABLE_AS_OBJC_BLOCK_MARSHALER.equals(owner)) {
                            owner = blockMarshalerName;
                        }
                        super.visitMethodInsn(opcode, owner, name, desc);
                    }
                    
                    @Override
                    public void visitLocalVariable(String name, String desc, String signature, Label start, Label end,
                            int index) {
                        // Ignored
                    }
                    
                    @Override
                    public void visitLineNumber(int line, Label start) {
                        // Ignored
                    }
                };
            }
        }, 0);
        
        cw.visitInnerClass(blockMarshalerName, clazz.getInternalName(), 
                blockMarshalerName.substring(clazz.getInternalName().length() + 1), 
                ACC_PUBLIC + ACC_STATIC);

        cw.visitEnd();
        
        File f = clazz.getPath().getGeneratedClassFile(blockMarshalerName);
        FileUtils.writeByteArrayToFile(f, cw.toByteArray());
        // The marshaler class is created after the class is compiled.
        // This prevents the triggering of a recompile of the class.
        f.setLastModified(clazz.lastModified());

        return blockMarshalerName;
    }

    private void generateBridgeMethod(Type[] actualGenericTypes, soot.Type[] unboxedTypes, 
            String[][] targetMethodAnnotations, ClassWriter cw) {

        List genericParamTypes = new ArrayList<>();
        genericParamTypes.add(new SootTypeType(LongType.v()));
        genericParamTypes.add(new SootTypeType(org_robovm_objc_ObjCBlock.getType()));
        for (int i = 1; i < unboxedTypes.length; i++) {
            Type t = unboxedTypes[i] instanceof PrimType 
                    ? new SootTypeType(unboxedTypes[i]) : actualGenericTypes[i];
            genericParamTypes.add(t);
        }
        Type genericReturnType = unboxedTypes[0] instanceof PrimType 
                ? new SootTypeType(unboxedTypes[0]) : actualGenericTypes[0];

        List rawParamTypes = new ArrayList<>();
        rawParamTypes.add(LongType.v());
        rawParamTypes.add(org_robovm_objc_ObjCBlock.getType());
        rawParamTypes.addAll(Arrays.asList(unboxedTypes).subList(1, unboxedTypes.length));

        String name = "invoke";
        String signature = getGenericSignature(genericParamTypes, genericReturnType);
        String desc = getDescriptor(rawParamTypes, unboxedTypes[0]);
        
        MethodVisitor mv = cw.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_NATIVE, name, desc, signature, null);
        AnnotationVisitor av = mv.visitAnnotation(BRIDGE, true);
        av.visit("dynamic", true);
        av.visitEnd();
        for (String s : targetMethodAnnotations[0]) {
            mv.visitAnnotation(s, true).visitEnd();
        }
        for (int i = 1; i < targetMethodAnnotations.length; i++) {
            for (String s : targetMethodAnnotations[i]) {
                // We add 2 parameters first so annotations for the first 
                // parameter must be added at index 2.
                mv.visitParameterAnnotation(i + 1, s, true).visitEnd();
            }
        }

        mv.visitParameterAnnotation(0, POINTER, true).visitEnd();
        mv.visitEnd();
    }
    
    private void generateCallbackMethod(String owner, SootMethod targetMethod, 
            Type[] actualGenericTypes, soot.Type[] actualRawTypes, soot.Type[] unboxedTypes, 
            Set usedBoxMethods, Set usedUnboxMethods,
            String[][] targetMethodAnnotations, ClassWriter cw) {
        
        String targetInterfaceName = Types.getInternalName(targetMethod.getDeclaringClass());

        List genericParamTypes = new ArrayList<>();
        genericParamTypes.add(new SootTypeType(org_robovm_objc_ObjCBlock.getType()));
        for (int i = 1; i < unboxedTypes.length; i++) {
            Type t = unboxedTypes[i] instanceof PrimType 
                    ? new SootTypeType(unboxedTypes[i]) : actualGenericTypes[i];
            genericParamTypes.add(t);
        }
        Type genericReturnType = unboxedTypes[0] instanceof PrimType 
                ? new SootTypeType(unboxedTypes[0]) : actualGenericTypes[0];

        List rawParamTypes = new ArrayList<>();
        rawParamTypes.add(org_robovm_objc_ObjCBlock.getType());
        rawParamTypes.addAll(Arrays.asList(unboxedTypes).subList(1, unboxedTypes.length));
        
        String name = "invoked";
        String signature = getGenericSignature(genericParamTypes, genericReturnType);
        String desc = getDescriptor(rawParamTypes, unboxedTypes[0]);
        
        MethodVisitor mv = cw.visitMethod(ACC_PRIVATE | ACC_STATIC, name, desc, signature, null);
        mv.visitAnnotation(CALLBACK, true).visitEnd();

        // create a dummy soot method just to be able to fetch @encoding information from it
        // using TypeEncoding
        SootMethod methodForEncoder = new SootMethod(name, rawParamTypes, unboxedTypes[0]);
        // just dummy declaring class
        methodForEncoder.setDeclaringClass(targetMethod.getDeclaringClass());
        methodForEncoder.setDeclared(true);
        for (String s : targetMethodAnnotations[0]) {
            mv.visitAnnotation(s, true).visitEnd();
            addRuntimeVisibleAnnotation(methodForEncoder, s);
        }
        for (int i = 1; i < targetMethodAnnotations.length; i++) {
            for (String s : targetMethodAnnotations[i]) {
                // We add 1 parameter first so annotations for the first 
                // parameter should be added at index 1.
                mv.visitParameterAnnotation(i, s, true).visitEnd();
                addRuntimeVisibleParameterAnnotation(methodForEncoder, i, s);
            }
        }

        // build and add encoding
        TypeEncoder encoder = new TypeEncoder();
        String typeEncoding = encoder.encode(methodForEncoder, !config.getArch().is32Bit());
        // attach @TypeEncoding annotation to allow ObjCBlock to use it to create proper description
        AnnotationVisitor av = mv.visitAnnotation(TYPE_ENCODING, true);
        av.visit("value", typeEncoding);
        av.visitEnd();

        mv.visitCode();
        
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKEVIRTUAL, "org/robovm/objc/ObjCBlock", "object", "()Ljava/lang/Object;");
        mv.visitTypeInsn(CHECKCAST, targetInterfaceName);
        
        for (int i = 1, var = 1; i < actualRawTypes.length; i++, var++) {
            soot.Type from = unboxedTypes[i];
            if (from == LongType.v()) {
                mv.visitVarInsn(LLOAD, var);
                var++; // longs need 2 slots
            } else if (from == FloatType.v()) {
                mv.visitVarInsn(FLOAD, var);
            } else if (from == DoubleType.v()) {
                mv.visitVarInsn(DLOAD, var);
                var++; // doubles need 2 slots
            } else if (from instanceof PrimType) {
                // boolean, byte, short, char and int are loaded using ILOAD
                mv.visitVarInsn(ILOAD, var);
            } else {
                // Reference
                mv.visitVarInsn(ALOAD, var);
            }
            
            soot.Type to = actualRawTypes[i];
            if (from != to) {
                // Box the value on the top of the stack.
                String boxDesc = getDescriptor(Collections.singletonList(from), to);
                usedBoxMethods.add(boxDesc);
                mv.visitMethodInsn(INVOKESTATIC, owner, "box", boxDesc);
            }
        }
        
        // Now the receiver and all arguments are on the stack (boxed if needed). 
        // Call the target method.
        
        mv.visitMethodInsn(INVOKEINTERFACE, targetInterfaceName, 
                targetMethod.getName(), getDescriptor(targetMethod));

        if (unboxedTypes[0] != actualRawTypes[0]) {
            mv.visitTypeInsn(CHECKCAST, getInternalName(actualRawTypes[0]));
            // Unbox the value on the top of the stack.
            String unboxDesc = getDescriptor(Collections.singletonList(actualRawTypes[0]), unboxedTypes[0]);
            usedUnboxMethods.add(unboxDesc);
            mv.visitMethodInsn(INVOKESTATIC, owner, "unbox", unboxDesc);
        }
        
        if (unboxedTypes[0] == VoidType.v()) {
            mv.visitInsn(RETURN);
        } else if (unboxedTypes[0] == LongType.v()) {
            mv.visitInsn(LRETURN);
        } else if (unboxedTypes[0] == FloatType.v()) {
            mv.visitInsn(FRETURN);
        } else if (unboxedTypes[0] == DoubleType.v()) {
            mv.visitInsn(DRETURN);
        } else if (unboxedTypes[0] instanceof PrimType) {
            mv.visitInsn(IRETURN);
        } else {
            mv.visitInsn(ARETURN);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    private String getGenericSignature(List genericParamTypes, Type genericReturnType) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (Type t : genericParamTypes) {
            sb.append(t.toGenericSignature());
        }
        sb.append(")");
        sb.append(genericReturnType.toGenericSignature());
        String s = sb.toString();
        if (s.contains("<")) {
            return s;
        }
        // Not a generic signature.
        return null;
    }

    private void generateTargetMethod(String owner, SootMethod targetMethod, 
            Type[] actualGenericTypes, soot.Type[] actualRawTypes, soot.Type[] unboxedTypes, 
            Set usedBoxMethods, Set usedUnboxMethods, ClassWriter cw) {

        String name = targetMethod.getName();
        String signature = getGenericSignature(
                Arrays.asList(actualGenericTypes).subList(1, actualGenericTypes.length), actualGenericTypes[0]);
        String desc = getDescriptor(targetMethod);
        
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, desc, signature, null);
        mv.visitCode();
        
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, owner, "objCBlock", "L" + getInternalName(org_robovm_objc_ObjCBlock) + ";");
        mv.visitMethodInsn(INVOKEVIRTUAL, getInternalName(org_robovm_objc_ObjCBlock), "invoke", "()J");
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, owner, "objCBlock", "L" + getInternalName(org_robovm_objc_ObjCBlock) + ";");
        
        for (int i = 1, var = 1; i < actualRawTypes.length; i++, var++) {
            soot.Type from = actualRawTypes[i];
            if (from == LongType.v()) {
                mv.visitVarInsn(LLOAD, var);
                var++; // longs need 2 slots
            } else if (from == FloatType.v()) {
                mv.visitVarInsn(FLOAD, var);
            } else if (from == DoubleType.v()) {
                mv.visitVarInsn(DLOAD, var);
                var++; // doubles need 2 slots
            } else if (from instanceof PrimType) {
                // boolean, byte, short, char and int are loaded using ILOAD
                mv.visitVarInsn(ILOAD, var);
            } else {
                // Reference
                mv.visitVarInsn(ALOAD, var);
            }
            
            soot.Type to = unboxedTypes[i];
            if (from != to) {
                mv.visitTypeInsn(CHECKCAST, getInternalName(from));
                // Unbox the value on the top of the stack.
                String unboxDesc = getDescriptor(Collections.singletonList(from), to);
                usedUnboxMethods.add(unboxDesc);
                mv.visitMethodInsn(INVOKESTATIC, owner, "unbox", unboxDesc);
            }
        }
        
        // Now the function pointer, block and all arguments are on the stack 
        // (unboxed if needed). Call the invoke() bridge method.
        
        List paramTypes = new ArrayList<>();
        paramTypes.add(LongType.v());
        paramTypes.add(org_robovm_objc_ObjCBlock.getType());
        paramTypes.addAll(Arrays.asList(unboxedTypes).subList(1, unboxedTypes.length));

        mv.visitMethodInsn(INVOKESTATIC, owner, 
                "invoke", getDescriptor(paramTypes, unboxedTypes[0]));
        
        if (unboxedTypes[0] != actualRawTypes[0]) {
            // Box the value on the top of the stack.
            String boxDesc = getDescriptor(Collections.singletonList(unboxedTypes[0]), actualRawTypes[0]);
            usedBoxMethods.add(boxDesc);
            mv.visitMethodInsn(INVOKESTATIC, owner, "box", boxDesc);
        }
        
        if (actualRawTypes[0] == VoidType.v()) {
            mv.visitInsn(RETURN);
        } else if (actualRawTypes[0] == LongType.v()) {
            mv.visitInsn(LRETURN);
        } else if (actualRawTypes[0] == FloatType.v()) {
            mv.visitInsn(FRETURN);
        } else if (actualRawTypes[0] == DoubleType.v()) {
            mv.visitInsn(DRETURN);
        } else if (actualRawTypes[0] instanceof PrimType) {
            mv.visitInsn(IRETURN);
        } else {
            mv.visitInsn(ARETURN);
        }
        
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }
    
    protected String getTargetMethodKey(SootMethod targetMethod, soot.Type[] actualTypes, 
            String[][] targetMethodAnnotations) {
        
        StringBuilder key = new StringBuilder(
            Symbols.methodSymbol(Types.getInternalName(targetMethod.getDeclaringClass()), 
                targetMethod.getName(), 
                Arrays.asList(actualTypes).subList(1, actualTypes.length), actualTypes[0]));
        
        for (String[] a : targetMethodAnnotations) {
            key.append(',');
            for (String s : a) {
                key.append(s);
            }
        }
        
        return key.toString();
    }
    
    private soot.Type unboxType(soot.Type t) {
        if (t instanceof RefType) {
            SootClass c = ((RefType) t).getSootClass();
            if (c.equals(java_lang_Boolean)) {
                return BooleanType.v();
            }
            if (c.equals(java_lang_Byte)) {
                return ByteType.v();
            }
            if (c.equals(java_lang_Short)) {
                return ShortType.v();
            }
            if (c.equals(java_lang_Character)) {
                return CharType.v();
            }
            if (c.equals(java_lang_Integer)) {
                return IntType.v();
            }
            if (c.equals(java_lang_Long)) {
                return LongType.v();
            }
            if (c.equals(java_lang_Float)) {
                return FloatType.v();
            }
            if (c.equals(java_lang_Double)) {
                return DoubleType.v();
            }
        }
        return t;
    }
    
    private soot.Type[] unboxTypes(soot.Type[] actualTypes) {
        soot.Type[] unboxed = actualTypes;
        for (int i = 0; i < actualTypes.length; i++) {
            soot.Type t = unboxType(actualTypes[i]);
            if (t != actualTypes[i]) {
                if (unboxed == actualTypes) {
                    unboxed = new soot.Type[actualTypes.length];
                    System.arraycopy(actualTypes, 0, unboxed, 0, actualTypes.length);
                }
                unboxed[i] = t;
            }
        }
        return unboxed;
    }

    private static List collectAbstractMethods(SootClass interfaze) {
        ArrayList result = new ArrayList<>();
        for (SootMethod m : interfaze.getMethods()) {
            if (m.isAbstract()) {
                result.add(m);
            }
        }
        for (SootClass c : interfaze.getInterfaces()) {
            result.addAll(collectAbstractMethods(c));
        }
        return result;
    }
    
    protected static SootMethod getBlockTargetMethod(SootMethod method, int paramIndex) {
        soot.Type type = method.getParameterType(paramIndex);
        if (!(type instanceof RefType)) {
            throw new CompilerException("@Block annotated parameter " + (paramIndex + 1) 
                    + " of method " + method + " must be of interface type");
        }
        SootClass blockType = ((RefType) type).getSootClass();
        if (!blockType.isInterface()) {
            throw new CompilerException("@Block annotated parameter " + (paramIndex + 1) 
                    + " of method " + method + " must be of interface type");
        }
        List allMethods = collectAbstractMethods(blockType);
        if (allMethods.isEmpty()) {
            throw new CompilerException("No abstract method found in interface " 
                    + blockType + " used in @Block annotated parameter " + (paramIndex + 1) 
                    + " of method " + method);
        }
        if (allMethods.size() > 1) {
            throw new CompilerException("More than 1 abstract method found in interface " 
                    + blockType + " used in @Block annotated parameter " + (paramIndex + 1) 
                    + " of method " + method);
        }
        
        return allMethods.get(0);
    }
    
    protected static SootMethod getBlockTargetMethod(SootMethod method) {
        soot.Type type = method.getReturnType();
        if (!(type instanceof RefType)) {
            throw new CompilerException("@Block annotated return type of method " 
                    + method + " must be of interface type");
        }
        SootClass blockType = ((RefType) type).getSootClass();
        if (!blockType.isInterface()) {
            throw new CompilerException("@Block annotated parameter return type " 
                    + "of method " + method + " must be of interface type");
        }
        List allMethods = collectAbstractMethods(blockType);
        if (allMethods.isEmpty()) {
            throw new CompilerException("No abstract method found in interface " 
                    + blockType + " used in @Block annotated return type of method " + method);
        }
        if (allMethods.size() > 1) {
            throw new CompilerException("More than 1 abstract method found in interface " 
                    + blockType + " used in @Block annotated return type of method " + method);
        }
        
        return allMethods.get(0);
    }
   
    
    protected static Type[] resolveTargetMethodSignature(SootMethod blockMethod, 
            SootMethod targetMethod, Type blockParamType) {
        
        if (targetMethod.getTag("SignatureTag") == null) {
            // Not a generic method.
            Type[] result = new Type[targetMethod.getParameterCount() + 1];
            result[0] = new SootTypeType(targetMethod.getReturnType());
            for (int i = 1; i < result.length; i++) {
                result[i] = new SootTypeType(targetMethod.getParameterType(i - 1));
            }
            return result;
        }
        
        SootClassType base = new SootClassType(targetMethod.getDeclaringClass());
        TypeVariable[] typeParameters = base.getTypeParameters();
        SootClassType offspring = null;
        Type[] actualArgs = null;
        if (blockParamType instanceof SootClassType) {
            offspring = (SootClassType) blockParamType;
            actualArgs = new Type[0];
        } else if (blockParamType instanceof ParameterizedType) {
            offspring = (SootClassType) ((ParameterizedType) blockParamType).getRawType();
            actualArgs = ((ParameterizedType) blockParamType).getActualTypeArguments();
        }

        Type[] resolvedArgs = resolveActualTypeArgs(offspring, base, actualArgs);

        // build map of generic parameter name to resolved argument values, resolve all type parameters to
        // flat values
        Map flatResolvedArgs = new HashMap<>();
        for (int i = 0; i < typeParameters.length; i++) {
            TypeVariable t = typeParameters[i];
            Type argValue = resolvedArgs[i];
            flatResolvedArgs.put(t.getName(), resolveMethodType(blockMethod, -2, argValue, null));
        }

        Type[] result = new Type[targetMethod.getParameterCount() + 1];
        SootMethodType targetMethodType = new SootMethodType(targetMethod);
        result[0] = resolveMethodType(blockMethod, -1, targetMethodType.getGenericReturnType(),
                flatResolvedArgs);
        Type[] genericParameterTypes = targetMethodType.getGenericParameterTypes();
        for (int i = 1; i < result.length; i++) {
            result[i] = resolveMethodType(blockMethod, i - 1, genericParameterTypes[i - 1], flatResolvedArgs);
        }
        
        return result;
    }
    
    /**
     * Resolves type of parameter
     * @param blockMethod invoke method of block, which parameters being converted, used for exception only
     * @param paramIndex index of method's parameter. used for exception message only
     * @param t method parameter type to resolve (might be normal type or TypeVariable)
     * @param resolvedArgs map of resolved generic arguments to be used to resolve method's TypeVariables
     */
    private static Type resolveMethodType(SootMethod blockMethod, int paramIndex, Type t, Map resolvedArgs) {
        
        if (t instanceof SootClassType) {
            return t;
        }
        if (t instanceof SootTypeType) {
            return t;
        }
        if (t instanceof TypeVariable) {
            if (resolvedArgs == null) {
                // there is no resolved arguments, resolving arguments itself -- resolve it bounds
                return resolveMethodType(blockMethod, paramIndex, ((TypeVariable) t).getBounds()[0], resolvedArgs);
            } else {
                // resolving method argument
                Type u = resolvedArgs.get(((TypeVariable) t).getName());
                if (u != null)
                    return u;
            }
            throw new CompilerException("Unresolved type variable " + t
                    + " in " 
                    + (paramIndex == -1 ? "return type" : (paramIndex == -2 ? "argument" : "parameter " + (paramIndex + 1)))
                    + " of @Block method " + blockMethod);
        }
        if (t instanceof WildcardType) {
            Type[] upperBounds = ((WildcardType) t).getUpperBounds();
            return resolveMethodType(blockMethod, paramIndex, upperBounds[0], resolvedArgs);
        }
        if (t instanceof ParameterizedType) {
            ImplForType pType = (ImplForType) t;
            ListOfTypes types = new ListOfTypes(pType.getActualTypeArguments().length);
            for (Type arg : pType.getActualTypeArguments()) {
                types.add(resolveMethodType(blockMethod, paramIndex, arg, resolvedArgs));
            }
            return new ImplForType((ImplForType) pType.getOwnerType(),
                    pType.getRawType().getSootClass().getName(), types);
        }
        if (t instanceof GenericArrayType) {
            Type componentType = resolveMethodType(blockMethod, paramIndex,
                    ((GenericArrayType) t).getGenericComponentType(), resolvedArgs);
            return new ImplForArray(componentType);
        }
        
        throw new CompilerException("Unresolved type " + t 
                + " in "
                + (paramIndex == -1 ? "return type" : (paramIndex == -2 ? "argument" : "parameter " + (paramIndex + 1)))
                + " of @Block method " + blockMethod);
    }

    private static soot.Type[] toRawTypes(Type[] t) {
        soot.Type[] result = new soot.Type[t.length];
        for (int i = 0; i < t.length; i++) {
            result[i] = toRawType(t[i]);
        }
        return result;
    }

    private static soot.Type toRawType(Type t) {
        if (t instanceof SootClassType) {
            return ((SootClassType) t).getSootClass().getType();
        }
        if (t instanceof SootTypeType) {
            return ((SootTypeType) t).getSootType();
        }
        if (t instanceof WildcardType) {
            Type[] upperBounds = ((WildcardType) t).getUpperBounds();
            return toRawType(upperBounds[0]);
        }
        if (t instanceof ParameterizedType) {
            return toRawType(((ParameterizedType) t).getRawType());
        }
        if (t instanceof GenericArrayType) {
            soot.Type componentType = toRawType(((GenericArrayType) t).getGenericComponentType());
            return componentType.makeArrayType();
        }
        // Should never end up here
        throw new CompilerException("Failed to get the raw type from a " 
                + t.getClass().getName() + " (" +t + ")");
    }
    
    private static int indexOf(String name, TypeVariable[] typeParameters) {
        for (int i = 0; i < typeParameters.length; i++) {
            if (name.equals(typeParameters[i].getName())) {
                return i;
            }
        }
        return -1;
    }
    
    /**
     * Resolves the actual generic type arguments for a base class, as viewed from a subclass or implementation.
     * 
     * @param offspring class or interface subclassing or extending the base type
     * @param base base class
     * @param actualArgs the actual type arguments passed to the offspring class
     * @return actual generic type arguments, must match the type parameters of the offspring class. If omitted, the
     * type parameters will be used instead.
     * 
     * This code was copied from
     * http://stackoverflow.com/questions/17297308/how-do-i-resolve-the-actual-type-for-a-generic-return-type-using-reflection
     * and changed slightly.
     */
    protected static Type[] resolveActualTypeArgs(SootClassType offspring, SootClassType base, 
            Type... actualArgs) {

        TypeVariable[] typeParameters = offspring.getTypeParameters();
        //  If actual types are omitted, the type parameters will be used instead.
        if (actualArgs.length == 0) {
            actualArgs = typeParameters;
        }
        // map type parameters into the actual types
        Map typeVariables = new HashMap<>();
        for (int i = 0; i < actualArgs.length; i++) {
            TypeVariable typeVariable = (TypeVariable) typeParameters[i];
            Type t = actualArgs[i];
            if (t instanceof WildcardType) {
                // If actual arg is ? it will have an upper bound of java.lang.Object but the
                // TypeVariable could specify a more specific type.
                Type upper = ((WildcardType) t).getUpperBounds()[0];
                if (upper instanceof SootClassType) {
                    if ("java.lang.Object".equals(((SootClassType) upper).getSootClass().getName())) {
                        actualArgs[i] = typeVariable.getBounds()[0];
                    }
                }
            }
            typeVariables.put(typeVariable.getName(), actualArgs[i]);
        }

        // Find direct ancestors (superclass, interfaces)
        List ancestors = new LinkedList();
        if (offspring.getGenericSuperclass() != null) {
            ancestors.add(offspring.getGenericSuperclass());
        }
        for (Type t : offspring.getGenericInterfaces()) {
            ancestors.add(t);
        }

        // Recurse into ancestors (superclass, interfaces)
        for (Type type : ancestors) {
            if (type instanceof SootClassType) {
                // ancestor is non-parameterized. Recurse only if it matches the base class.
                SootClassType ancestorClass = (SootClassType) type;
                if (base.isAssignableFrom(ancestorClass)) {
                    Type[] result = resolveActualTypeArgs(ancestorClass, base);
                    if (result != null) {
                        return result;
                    }
                }
            }
            if (type instanceof ParameterizedType) {
                // ancestor is parameterized. Recurse only if the raw type matches the base class.
                ParameterizedType parameterizedType = (ParameterizedType) type;
                Type rawType = parameterizedType.getRawType();
                if (rawType instanceof SootClassType) {
                    SootClassType rawTypeClass = (SootClassType) rawType;
                    if (base.isAssignableFrom(rawTypeClass)) {
                        parameterizedType = resolveParameterizedType(parameterizedType, typeVariables);
                        Type[] result = resolveActualTypeArgs(rawTypeClass, base, 
                                parameterizedType.getActualTypeArguments());
                        if (result != null) {
                            return result;
                        }
                    }
                }
            }
        }

        // we have a result if we reached the base class.
        return offspring.equals(base) ? actualArgs : null;
    }
    
    /**
     * Loops through all type arguments and replaces type variables with the
     * actually known types.
     */
    private static ParameterizedType resolveParameterizedType(ParameterizedType parameterizedType, Map typeVariables) {
        List resolvedTypes = new LinkedList();
        for (Type t : parameterizedType.getActualTypeArguments()) {
            if (t instanceof TypeVariable) {
                Type resolvedType = typeVariables.get(((TypeVariable) t).getName());
                if (resolvedType instanceof ParameterizedType) {
                    resolvedType = resolveParameterizedType((ParameterizedType) resolvedType, typeVariables);
                }
                resolvedTypes.add(resolvedType != null ? resolvedType : t);
            } else if (t instanceof ParameterizedType) {
                ParameterizedType pType = (ParameterizedType) t;
                resolvedTypes.add(resolveParameterizedType(pType, typeVariables));
            } else {
                resolvedTypes.add(t);
            }
        }
        ListOfTypes types = new ListOfTypes(resolvedTypes.toArray(new Type[resolvedTypes.size()]));
        return new ImplForType(null, 
                parameterizedType.getRawType().toString(), types);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy