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

org.teavm.model.util.AsyncMethodFinder Maven / Gradle / Ivy

There is a newer version: 0.2.8
Show newest version
/*
 *  Copyright 2015 Alexey Andreev.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.teavm.model.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teavm.callgraph.CallGraph;
import org.teavm.callgraph.CallGraphNode;
import org.teavm.callgraph.CallSite;
import org.teavm.dependency.DependencyInfo;
import org.teavm.interop.Async;
import org.teavm.model.ClassReader;
import org.teavm.model.ElementModifier;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.ProgramReader;
import org.teavm.model.VariableReader;
import org.teavm.model.instructions.AbstractInstructionReader;
import org.teavm.runtime.Fiber;

public class AsyncMethodFinder {
    private Set asyncMethods = new HashSet<>();
    private DependencyInfo dependency;
    private Map asyncFamilyMethods = new HashMap<>();
    private Set readonlyAsyncMethods = Collections.unmodifiableSet(asyncMethods);
    private Set readonlyAsyncFamilyMethods = Collections.unmodifiableSet(asyncFamilyMethods.keySet());
    private CallGraph callGraph;
    private ListableClassReaderSource classSource;
    private boolean hasAsyncMethods;

    public AsyncMethodFinder(CallGraph callGraph, DependencyInfo dependency) {
        this.callGraph = callGraph;
        this.dependency = dependency;
    }

    public Set getAsyncMethods() {
        return readonlyAsyncMethods;
    }

    public Set getAsyncFamilyMethods() {
        return readonlyAsyncFamilyMethods;
    }

    public void find(ListableClassReaderSource classSource) {
        this.classSource = classSource;
        hasAsyncMethods = findAsyncMethods();
        for (String clsName : classSource.getClassNames()) {
            ClassReader cls = classSource.get(clsName);
            for (MethodReader method : cls.getMethods()) {
                if (!dependency.getReachableMethods().contains(method.getReference())
                        || asyncMethods.contains(method.getReference())) {
                    continue;
                }
                if (method.getAnnotations().get(Async.class.getName()) != null) {
                    add(method.getReference(), new CallStack(method.getReference(), null));
                }
            }
        }
        if (hasAsyncMethods) {
            for (String clsName : classSource.getClassNames()) {
                ClassReader cls = classSource.get(clsName);
                for (MethodReader method : cls.getMethods()) {
                    if (!dependency.getReachableMethods().contains(method.getReference())
                            || asyncMethods.contains(method.getReference()) || method.getProgram() == null) {
                        continue;
                    }
                    if (hasMonitor(method)) {
                        add(method.getReference(), new CallStack(method.getReference(), null));
                    }
                }
            }
        }
        for (MethodReference method : asyncMethods) {
            addOverriddenToFamily(method);
        }
        for (String clsName : classSource.getClassNames()) {
            ClassReader cls = classSource.get(clsName);
            for (MethodReader method : cls.getMethods()) {
                addToFamily(method.getReference());
            }
        }
        for (Map.Entry entry : new ArrayList<>(asyncFamilyMethods.entrySet())) {
            if (!entry.getValue()) {
                asyncFamilyMethods.remove(entry.getKey());
            }
        }
    }

    private boolean findAsyncMethods() {
        boolean result = false;
        loop: for (String clsName : classSource.getClassNames()) {
            ClassReader cls = classSource.get(clsName);
            for (MethodReader method : cls.getMethods()) {
                if (!asyncMethods.contains(method.getReference()) || method.getProgram() == null) {
                    continue;
                }
                if (hasMonitor(method)) {
                    result = true;
                    break loop;
                }
            }
        }
        boolean hasThreads = dependency.getReachableMethods().contains(new MethodReference(
                Thread.class, "start", void.class));
        return result && hasThreads;
    }

    public boolean hasAsyncMethods() {
        return hasAsyncMethods;
    }

    private boolean hasMonitor(MethodReader method) {
        if (method.hasModifier(ElementModifier.SYNCHRONIZED)) {
            return true;
        }
        ProgramReader program = method.getProgram();
        AsyncInstructionReader insnReader = new AsyncInstructionReader();
        for (int i = 0; i < program.basicBlockCount(); ++i) {
            program.basicBlockAt(i).readAllInstructions(insnReader);
            if (insnReader.async) {
                return true;
            }
        }
        return false;
    }

    private void add(MethodReference methodRef, CallStack stack) {
        if (methodRef.getClassName().equals(Fiber.class.getName())) {
            return;
        }

        if (!asyncMethods.add(methodRef)) {
            return;
        }
        CallGraphNode node = callGraph.getNode(methodRef);
        if (node == null) {
            return;
        }
        ClassReader cls = classSource.get(methodRef.getClassName());
        if (cls == null) {
            return;
        }
        MethodReader method = cls.getMethod(methodRef.getDescriptor());
        if (method == null) {
            return;
        }

        if (!hasAsyncMethods && methodRef.getClassName().equals("java.lang.Object")
                && (methodRef.getName().equals("monitorEnter") || methodRef.getName().equals("monitorExit"))) {
            return;
        }
        for (CallSite callSite : node.getCallerCallSites()) {
            for (CallGraphNode caller : callSite.getCallers()) {
                add(caller.getMethod(), new CallStack(caller.getMethod(), stack));
            }
        }
    }

    static class CallStack {
        MethodReference method;
        CallStack next;

        CallStack(MethodReference method, CallStack next) {
            this.method = method;
            this.next = next;
        }

        @Override
        public String toString() {
            StringBuilder sb = new StringBuilder();
            CallStack stack = this;
            while (stack != null) {
                sb.append("\n    calling " + stack.method);
                stack = stack.next;
            }
            return sb.toString();
        }
    }

    private void addOverriddenToFamily(MethodReference methodRef) {
        asyncFamilyMethods.put(methodRef, true);
        ClassReader cls = classSource.get(methodRef.getClassName());
        if (cls == null) {
            return;
        }
        for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) {
            addOverriddenToFamily(overriddenMethod);
        }
    }

    private boolean addToFamily(MethodReference methodRef) {
        Boolean cachedResult = asyncFamilyMethods.get(methodRef);
        if (cachedResult != null) {
            return cachedResult;
        }
        boolean result = addToFamilyCacheMiss(methodRef);
        asyncFamilyMethods.put(methodRef, result);
        return result;
    }

    private boolean addToFamilyCacheMiss(MethodReference methodRef) {
        if (asyncMethods.contains(methodRef)) {
            return true;
        }
        ClassReader cls = classSource.get(methodRef.getClassName());
        if (cls == null) {
            return false;
        }
        for (MethodReference overriddenMethod : findOverriddenMethods(cls, methodRef.getDescriptor())) {
            if (addToFamily(overriddenMethod)) {
                return true;
            }
        }
        return false;
    }

    private Set findOverriddenMethods(ClassReader cls, MethodDescriptor methodDesc) {
        List parents = new ArrayList<>();
        if (cls.getParent() != null) {
            parents.add(cls.getParent());
        }
        parents.addAll(cls.getInterfaces());

        Set visited = new HashSet<>();
        Set overridden = new HashSet<>();
        for (String parent : parents) {
            findOverriddenMethods(parent, methodDesc, overridden, visited);
        }
        return overridden;
    }

    private void findOverriddenMethods(String className, MethodDescriptor methodDesc, Set result,
            Set visited) {
        if (!visited.add(className)) {
            return;
        }
        if (methodDesc.getName().equals("") || methodDesc.getName().equals("")) {
            return;
        }
        ClassReader cls = classSource.get(className);
        if (cls == null) {
            return;
        }
        MethodReader method = cls.getMethod(methodDesc);
        if (method != null) {
            if (!method.hasModifier(ElementModifier.STATIC) && !method.hasModifier(ElementModifier.FINAL)) {
                result.add(method.getReference());
            }
        } else {
            if (cls.getParent() != null) {
                findOverriddenMethods(cls.getParent(), methodDesc, result, visited);
            }
            for (String iface : cls.getInterfaces()) {
                findOverriddenMethods(iface, methodDesc, result, visited);
            }
        }
    }

    static class AsyncInstructionReader extends AbstractInstructionReader {
        boolean async;

        @Override
        public void monitorEnter(VariableReader objectRef) {
            async = true;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy