Java tutorial
/* * Copyright 2012 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.dependency; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; import org.objectweb.asm.tree.ClassNode; import org.teavm.callgraph.CallGraph; import org.teavm.callgraph.DefaultCallGraph; import org.teavm.callgraph.DefaultCallGraphNode; import org.teavm.common.CachedMapper; import org.teavm.common.Mapper; import org.teavm.common.ServiceRepository; import org.teavm.diagnostics.Diagnostics; import org.teavm.model.AnnotationReader; import org.teavm.model.CallLocation; import org.teavm.model.ClassHolder; import org.teavm.model.ClassHolderTransformer; import org.teavm.model.ClassReader; import org.teavm.model.ClassReaderSource; import org.teavm.model.ElementModifier; import org.teavm.model.FieldHolder; import org.teavm.model.FieldReader; import org.teavm.model.FieldReference; import org.teavm.model.InstructionLocation; import org.teavm.model.MethodDescriptor; import org.teavm.model.MethodHolder; import org.teavm.model.MethodReader; import org.teavm.model.MethodReference; import org.teavm.model.Program; import org.teavm.model.ValueType; import org.teavm.model.util.ModelUtils; import org.teavm.model.util.ProgramUtils; import org.teavm.optimization.UnreachableBasicBlockEliminator; import org.teavm.parsing.Parser; public class DependencyChecker implements DependencyInfo { static final boolean shouldLog = System.getProperty("org.teavm.logDependencies", "false").equals("true"); private int classNameSuffix; private DependencyClassSource classSource; private ClassLoader classLoader; private Mapper<MethodReference, MethodHolder> methodReaderCache; private Mapper<FieldReference, FieldHolder> fieldReaderCache; private CachedMapper<MethodReference, MethodDependency> methodCache; private CachedMapper<FieldReference, FieldDependency> fieldCache; private CachedMapper<String, ClassDependency> classCache; private List<DependencyListener> listeners = new ArrayList<>(); private ServiceRepository services; private Queue<Runnable> tasks = new ArrayDeque<>(); List<DependencyType> types = new ArrayList<>(); private Map<String, DependencyType> typeMap = new HashMap<>(); private DependencyCheckerInterruptor interruptor; private boolean interrupted; private Diagnostics diagnostics; DefaultCallGraph callGraph = new DefaultCallGraph(); private DependencyAgent agent; List<DependencyNode> nodes = new ArrayList<>(); Map<MethodReference, BootstrapMethodSubstitutor> bootstrapMethodSubstitutors = new HashMap<>(); private boolean completing; public DependencyChecker(ClassReaderSource classSource, ClassLoader classLoader, ServiceRepository services, Diagnostics diagnostics) { this.diagnostics = diagnostics; this.classSource = new DependencyClassSource(classSource, diagnostics); this.classLoader = classLoader; this.services = services; methodReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutable(preimage)); fieldReaderCache = new CachedMapper<>(preimage -> this.classSource.resolveMutable(preimage)); methodCache = new CachedMapper<>(preimage -> { MethodHolder method = methodReaderCache.map(preimage); if (method != null && !method.getReference().equals(preimage)) { return methodCache.map(method.getReference()); } return createMethodDep(preimage, method); }); fieldCache = new CachedMapper<>(preimage -> { FieldReader field = fieldReaderCache.map(preimage); if (field != null && !field.getReference().equals(preimage)) { return fieldCache.map(field.getReference()); } return createFieldNode(preimage, field); }); classCache = new CachedMapper<>(this::createClassDependency); agent = new DependencyAgent(this); } public DependencyAgent getAgent() { return agent; } public DependencyCheckerInterruptor getInterruptor() { return interruptor; } public void setInterruptor(DependencyCheckerInterruptor interruptor) { this.interruptor = interruptor; } public boolean wasInterrupted() { return interrupted; } public DependencyType getType(String name) { DependencyType type = typeMap.get(name); if (type == null) { type = new DependencyType(this, name, types.size()); types.add(type); typeMap.put(name, type); } return type; } public DependencyNode createNode() { DependencyNode node = new DependencyNode(this); nodes.add(node); return node; } @Override public ClassReaderSource getClassSource() { return classSource; } @Override public ClassLoader getClassLoader() { return classLoader; } public String generateClassName() { return "$$teavm_generated_class$$" + classNameSuffix++; } public String submitClassFile(byte[] data) { ClassNode node = new ClassNode(); org.objectweb.asm.ClassReader reader = new org.objectweb.asm.ClassReader(data); reader.accept(node, 0); submitClass(Parser.parseClass(node)); return node.name; } public void submitClass(ClassHolder cls) { if (completing) { throw new IllegalStateException("Can't submit class during completion phase"); } classSource.submit(ModelUtils.copyClass(cls)); } public void submitMethod(MethodReference methodRef, Program program) { if (!completing) { ClassHolder cls = classSource.get(methodRef.getClassName()); if (cls == null) { throw new IllegalArgumentException("Class not found: " + methodRef.getClassName()); } if (cls.getMethod(methodRef.getDescriptor()) != null) { throw new IllegalArgumentException("Method already exists: " + methodRef.getClassName()); } MethodHolder method = new MethodHolder(methodRef.getDescriptor()); method.getModifiers().add(ElementModifier.STATIC); method.setProgram(ProgramUtils.copy(program)); new UnreachableBasicBlockEliminator().optimize(program); cls.addMethod(method); } else { MethodDependency dep = getMethod(methodRef); if (dep == null) { throw new IllegalArgumentException("Method was not reached: " + methodRef); } MethodHolder method = dep.method; if (!method.hasModifier(ElementModifier.NATIVE)) { throw new IllegalArgumentException("Method is not native: " + methodRef); } if (!dep.used) { return; } method.getModifiers().remove(ElementModifier.NATIVE); method.setProgram(ProgramUtils.copy(program)); new UnreachableBasicBlockEliminator().optimize(method.getProgram()); dep.used = false; lock(dep, false); tasks.add(() -> { DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this); graphBuilder.buildGraph(dep); dep.used = true; }); processQueue(); } } public void addDependencyListener(DependencyListener listener) { listeners.add(listener); listener.started(agent); } public void addClassTransformer(ClassHolderTransformer transformer) { classSource.addTransformer(transformer); } public void addEntryPoint(MethodReference methodRef, String... argumentTypes) { ValueType[] parameters = methodRef.getDescriptor().getParameterTypes(); if (parameters.length + 1 != argumentTypes.length) { throw new IllegalArgumentException( "argumentTypes length does not match the number of method's arguments"); } MethodDependency method = linkMethod(methodRef, null); method.use(); DependencyNode[] varNodes = method.getVariables(); varNodes[0].propagate(getType(methodRef.getClassName())); for (int i = 0; i < argumentTypes.length; ++i) { varNodes[i + 1].propagate(getType(argumentTypes[i])); } } void schedulePropagation(DependencyConsumer consumer, DependencyType type) { tasks.add(() -> consumer.consume(type)); } void schedulePropagation(DependencyConsumer consumer, DependencyType[] types) { tasks.add(() -> { for (DependencyType type : types) { consumer.consume(type); } }); } private Set<String> classesAddedByRoot = new HashSet<>(); public ClassDependency linkClass(String className, CallLocation callLocation) { if (completing && getClass(className) == null) { throw new IllegalStateException("Can't link class during completion phase"); } ClassDependency dep = classCache.map(className); boolean added = true; if (callLocation != null && callLocation.getMethod() != null) { DefaultCallGraphNode callGraphNode = callGraph.getNode(callLocation.getMethod()); if (!addClassAccess(callGraphNode, className, callLocation.getSourceLocation())) { added = false; } } else { added = classesAddedByRoot.add(className); } if (!dep.isMissing() && added) { tasks.add(() -> { for (DependencyListener listener : listeners) { listener.classReached(agent, className, callLocation); } }); } return dep; } private boolean addClassAccess(DefaultCallGraphNode node, String className, InstructionLocation loc) { if (!node.addClassAccess(className, loc)) { return false; } ClassReader cls = classSource.get(className); if (cls != null) { if (cls.getParent() != null && !cls.getParent().equals(cls.getName())) { addClassAccess(node, cls.getParent(), loc); } for (String iface : cls.getInterfaces()) { addClassAccess(node, iface, loc); } } return true; } private ClassDependency createClassDependency(String className) { ClassReader cls = classSource.get(className); ClassDependency dependency = new ClassDependency(this, className, cls); if (!dependency.isMissing()) { if (cls.getParent() != null && !cls.getParent().equals(className)) { linkClass(cls.getParent(), null); } for (String ifaceName : cls.getInterfaces()) { linkClass(ifaceName, null); } } return dependency; } private Set<MethodReference> methodsAddedByRoot = new HashSet<>(); public MethodDependency linkMethod(MethodReference methodRef, CallLocation callLocation) { if (methodRef == null) { throw new IllegalArgumentException(); } MethodReader methodReader = methodReaderCache.map(methodRef); if (methodReader != null) { methodRef = methodReader.getReference(); } if (completing && getMethod(methodRef) == null) { throw new IllegalStateException("Can't submit class during completion phase"); } callGraph.getNode(methodRef); boolean added = true; if (callLocation != null && callLocation.getMethod() != null) { added = callGraph.getNode(callLocation.getMethod()).addCallSite(methodRef, callLocation.getSourceLocation()); } else { added = methodsAddedByRoot.add(methodRef); } MethodDependency graph = methodCache.map(methodRef); if (!graph.isMissing() && added) { for (DependencyListener listener : listeners) { listener.methodReached(agent, graph, callLocation); } activateDependencyPlugin(graph, callLocation); } return graph; } void initClass(ClassDependency cls, CallLocation callLocation) { ClassReader reader = cls.getClassReader(); MethodReader method = reader.getMethod(new MethodDescriptor("<clinit>", void.class)); if (method != null) { tasks.add(() -> linkMethod(method.getReference(), callLocation).use()); } } private MethodDependency createMethodDep(MethodReference methodRef, MethodHolder method) { ValueType[] arguments = methodRef.getParameterTypes(); int paramCount = arguments.length + 1; DependencyNode[] parameterNodes = new DependencyNode[arguments.length + 1]; for (int i = 0; i < parameterNodes.length; ++i) { parameterNodes[i] = createNode(); parameterNodes[i].method = methodRef; if (shouldLog) { parameterNodes[i].setTag(methodRef + ":" + i); } } DependencyNode resultNode; if (methodRef.getDescriptor().getResultType() == ValueType.VOID) { resultNode = null; } else { resultNode = createNode(); resultNode.method = methodRef; if (shouldLog) { resultNode.setTag(methodRef + ":RESULT"); } } DependencyNode thrown = createNode(); thrown.method = methodRef; if (shouldLog) { thrown.setTag(methodRef + ":THROWN"); } MethodDependency dep = new MethodDependency(this, parameterNodes, paramCount, resultNode, thrown, method, methodRef); if (method != null) { tasks.add(() -> { CallLocation caller = new CallLocation(dep.getMethod().getReference()); linkClass(dep.getMethod().getOwnerName(), caller).initClass(caller); }); } return dep; } void scheduleMethodAnalysis(MethodDependency dep) { tasks.add(() -> { DependencyGraphBuilder graphBuilder = new DependencyGraphBuilder(DependencyChecker.this); graphBuilder.buildGraph(dep); }); } @Override public Collection<MethodReference> getReachableMethods() { return methodCache.getCachedPreimages(); } @Override public Collection<FieldReference> getReachableFields() { return fieldCache.getCachedPreimages(); } @Override public Collection<String> getReachableClasses() { return classCache.getCachedPreimages(); } private Set<FieldReference> fieldsAddedByRoot = new HashSet<>(); public FieldDependency linkField(FieldReference fieldRef, CallLocation location) { if (completing) { throw new IllegalStateException("Can't submit class during completion phase"); } boolean added = true; if (location != null) { added = callGraph.getNode(location.getMethod()).addFieldAccess(fieldRef, location.getSourceLocation()); } else { added = fieldsAddedByRoot.add(fieldRef); } FieldDependency dep = fieldCache.map(fieldRef); if (!dep.isMissing()) { tasks.add(() -> linkClass(fieldRef.getClassName(), location).initClass(location)); } if (!dep.isMissing() && added) { for (DependencyListener listener : listeners) { listener.fieldReached(agent, dep, location); } } return dep; } @Override public FieldDependency getField(FieldReference fieldRef) { return fieldCache.getKnown(fieldRef); } @Override public ClassDependency getClass(String className) { return classCache.getKnown(className); } private FieldDependency createFieldNode(FieldReference fieldRef, FieldReader field) { DependencyNode node = createNode(); if (shouldLog) { node.setTag(fieldRef.getClassName() + "#" + fieldRef.getFieldName()); } FieldDependency dep = new FieldDependency(node, field, fieldRef); if (!dep.isMissing()) { tasks.add(() -> linkClass(fieldRef.getClassName(), null).initClass(null)); } return dep; } private void activateDependencyPlugin(MethodDependency methodDep, CallLocation location) { attachDependencyPlugin(methodDep); if (methodDep.dependencyPlugin != null) { methodDep.dependencyPlugin.methodReached(agent, methodDep, location); } } private void attachDependencyPlugin(MethodDependency methodDep) { if (methodDep.dependencyPluginAttached) { return; } methodDep.dependencyPluginAttached = true; AnnotationReader depAnnot = methodDep.getMethod().getAnnotations().get(PluggableDependency.class.getName()); if (depAnnot == null) { return; } ValueType depType = depAnnot.getValue("value").getJavaClass(); String depClassName = ((ValueType.Object) depType).getClassName(); Class<?> depClass; try { depClass = Class.forName(depClassName, true, classLoader); } catch (ClassNotFoundException e) { throw new RuntimeException("Dependency plugin not found: " + depClassName, e); } try { methodDep.dependencyPlugin = (DependencyPlugin) depClass.newInstance(); } catch (IllegalAccessException | InstantiationException e) { throw new RuntimeException("Can't instantiate dependency plugin " + depClassName, e); } } @Override public MethodDependency getMethod(MethodReference methodRef) { return methodCache.getKnown(methodRef); } @Override public MethodDependency getMethodImplementation(MethodReference methodRef) { MethodReader method = methodReaderCache.map(methodRef); return method != null ? methodCache.getKnown(method.getReference()) : null; } private void processQueue() { if (interrupted) { return; } int index = 0; while (!tasks.isEmpty()) { tasks.poll().run(); if (++index == 100) { if (interruptor != null && !interruptor.shouldContinue()) { interrupted = true; break; } index = 0; } } } public void processDependencies() { interrupted = false; processQueue(); if (!interrupted) { completing = true; lock(); for (DependencyListener listener : listeners) { listener.completing(agent); } } } private void lock() { for (MethodReference method : getReachableMethods()) { lock(getMethod(method), true); } for (FieldReference field : getReachableFields()) { lock(getField(field)); } } private void lock(MethodDependency dep, boolean lock) { for (DependencyNode node : dep.variableNodes) { if (node != null) { node.locked = lock; } } if (dep.resultNode != null) { dep.resultNode.locked = lock; } if (dep.thrown != null) { dep.thrown.locked = lock; } } private void lock(FieldDependency dep) { dep.value.locked = true; } public <T> T getService(Class<T> type) { return services.getService(type); } public Diagnostics getDiagnostics() { return diagnostics; } @Override public CallGraph getCallGraph() { return callGraph; } public void addBootstrapMethodSubstitutor(MethodReference method, BootstrapMethodSubstitutor substitutor) { bootstrapMethodSubstitutors.put(method, substitutor); } }