org.qbicc.plugin.reachability.ReachabilityInfo Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of qbicc-plugin-reachability Show documentation
Show all versions of qbicc-plugin-reachability Show documentation
Support for evaluating program reachability
package org.qbicc.plugin.reachability;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import org.jboss.logging.Logger;
import org.qbicc.context.AttachmentKey;
import org.qbicc.context.CompilationContext;
import org.qbicc.plugin.coreclasses.CoreClasses;
import org.qbicc.type.definition.LoadedTypeDefinition;
import org.qbicc.type.definition.element.ConstructorElement;
import org.qbicc.type.definition.element.ExecutableElement;
import org.qbicc.type.definition.element.MethodElement;
/**
* ReachabilityInfo tracks the types and methods that have been determined to be reachable
* and/or instantiatable from a program entrypoint.
*
* A class or interface is called reachable if a reference to a
* static program element it defines or its java.lang.Class instance
* could be executed by an invokable method.
*
* A class is called instantiated if a `new` of the class (or one of its subclasses)
* could be executed by an invokable method.
*
* A reachable static method is considered to be invokable.
* A reachable interface method is considered to be invokable.
* A reachable instance method of a class is only considered to be invokable if its defining class is instantiated.
*
* If a class is reachable, its superclass is also considered to be reachable.
* Class reachability does not imply reachability of its implemented interfaces.
*
* If a class is instantiated, then its superclass is also considered to be instantiated.
* If a class is instantiated, then both its directly implemented interfaces and all of their
* ancestor interfaces are considered to be reachable.
* An instantiated class is always also a reachable class.
*
* All classes and interfaces that are reachable after the ANALYZE phase completes
* will be assigned typeIds.
*
* All methods that are invokable at the end of the ANALYZE phase
* will be carried through the rest of the phases and compiled.
*/
public class ReachabilityInfo {
static final Logger LOGGER = Logger.getLogger("org.qbicc.plugin.reachability");
private static final AttachmentKey KEY = new AttachmentKey<>();
// Tracks reachable classes and their (direct) reachable subclasses
private final Map> classHierarchy = new ConcurrentHashMap<>();
// Tracks reachable interfaces and their (direct) reachable implementors
private final Map> interfaceHierarchy = new ConcurrentHashMap<>();
// Tracks actually instantiated classes
private final Set instantiatedClasses = ConcurrentHashMap.newKeySet();
// Tracks classes and interfaces whose could be invoked at runtime
private final Set initializedTypes = ConcurrentHashMap.newKeySet();
// Set of invokable instance methods
private final Set invokableMethods = ConcurrentHashMap.newKeySet();
private final ReachabilityAnalysis analysis;
private final CompilationContext ctxt;
private ReachabilityInfo(final CompilationContext ctxt) {
this.ctxt = ctxt;
// TODO: Currently hardwired to RTA; eventually will support multiple analysis algorithms
this.analysis = new RapidTypeAnalysis(this, ctxt);
}
public static ReachabilityInfo get(CompilationContext ctxt) {
ReachabilityInfo info = ctxt.getAttachment(KEY);
if (info == null) {
info = new ReachabilityInfo(ctxt);
ReachabilityInfo appearing = ctxt.putAttachmentIfAbsent(KEY, info);
if (appearing != null) {
info = appearing;
}
}
return info;
}
public static void clear(CompilationContext ctxt) {
ReachabilityInfo info = get(ctxt);
info.classHierarchy.clear();
info.interfaceHierarchy.clear();
info.instantiatedClasses.clear();
info.initializedTypes.clear();
info.invokableMethods.clear();
info.analysis.clear();
}
public static void reportStats(CompilationContext ctxt) {
ReachabilityInfo info = get(ctxt);
LOGGER.debug("Reachability Statistics");
LOGGER.debugf(" Reachable interfaces: %s", info.interfaceHierarchy.size());
LOGGER.debugf(" Reachable classes: %s", info.classHierarchy.size());
LOGGER.debugf(" Instantiated classes: %s", info.instantiatedClasses.size());
LOGGER.debugf(" Initialized types: %s", info.initializedTypes.size());
LOGGER.debugf(" Invokable instance methods: %s", info.invokableMethods.size());
info.analysis.reportStats();
}
// We force some fundamental types to be considered reachable even if the program doesn't use them.
// This simplifies the implementation of the core runtime.
public static void forceCoreClassesReachable(CompilationContext ctxt) {
ReachabilityInfo info = get(ctxt);
CoreClasses cc = CoreClasses.get(ctxt);
LOGGER.debugf("Forcing all array types reachable/instantiated");
String[] desc = { "[Z", "[B", "[C", "[S", "[I", "[F", "[J", "[D", "[ref" };
LoadedTypeDefinition obj = ctxt.getBootstrapClassContext().findDefinedType("java/lang/Object").load();
LoadedTypeDefinition cloneable = ctxt.getBootstrapClassContext().findDefinedType("java/lang/Cloneable").load();
LoadedTypeDefinition serializable = ctxt.getBootstrapClassContext().findDefinedType("java/io/Serializable").load();
info.analysis.processInstantiatedClass(obj, true, false,null);
info.analysis.processClassInitialization(obj);
info.addReachableInterface(cloneable);
info.addReachableInterface(serializable);
for (String d : desc) {
LoadedTypeDefinition at = cc.getArrayLoadedTypeDefinition(d);
info.addInterfaceEdge(at, cloneable);
info.addInterfaceEdge(at, serializable);
info.addInitializedType(at);
info.analysis.processInstantiatedClass(at, true, false,null);
}
LOGGER.debugf("Forcing java.lang.Class reachable/instantiated");
LoadedTypeDefinition clz = ctxt.getBootstrapClassContext().findDefinedType("java/lang/Class").load();
info.analysis.processInstantiatedClass(clz, true, false,null);
info.analysis.processClassInitialization(clz);
LOGGER.debugf("Forcing jdk.internal.misc.Unsafe reachable/instantiated");
LoadedTypeDefinition unsafe = ctxt.getBootstrapClassContext().findDefinedType("jdk/internal/misc/Unsafe").load();
info.analysis.processInstantiatedClass(unsafe, true, false,null);
info.analysis.processClassInitialization(unsafe);
// The main Thread is instantiated in native code, and thus not visible to analysis.
LOGGER.debugf("Forcing java.lang.Thread reachable/instantiated");
LoadedTypeDefinition thr = ctxt.getBootstrapClassContext().findDefinedType("java/lang/Thread").load();
info.analysis.processInstantiatedClass(thr, true, false,null);
info.analysis.processClassInitialization(thr);
}
public static void processAutoQueuedElement(ExecutableElement elem) {
if (elem instanceof MethodElement me) {
ReachabilityInfo info = get(elem.getEnclosingType().getContext().getCompilationContext());
if (me.isStatic()) {
info.analysis.processReachableStaticInvoke(me, null);
} else {
info.analysis.processReachableInstanceMethodInvoke(me, null);
}
} else if (elem instanceof ConstructorElement ce) {
ReachabilityInfo info = get(elem.getEnclosingType().getContext().getCompilationContext());
info.analysis.processReachableConstructorInvoke(ce.getEnclosingType().load(), ce, null);
}
}
public boolean isInvokableMethod(MethodElement meth) {
return invokableMethods.contains(meth);
}
public boolean isReachableClass(LoadedTypeDefinition type) {
return classHierarchy.containsKey(type);
}
public boolean isReachableInterface(LoadedTypeDefinition type) {
return interfaceHierarchy.containsKey(type);
}
public boolean isInitializedType(LoadedTypeDefinition type) {
return initializedTypes.contains(type);
}
public boolean isInstantiatedClass(LoadedTypeDefinition type) {
return instantiatedClasses.contains(type);
}
public void visitReachableInterfaces(Consumer function) {
for (LoadedTypeDefinition i : interfaceHierarchy.keySet()) {
function.accept(i);
}
}
public void visitReachableImplementors(LoadedTypeDefinition type, Consumer function) {
Set implementors = interfaceHierarchy.get(type);
if (implementors == null) return;
Set toProcess = new HashSet<>();
collectImplementors(type, toProcess);
for (LoadedTypeDefinition cls : toProcess) {
function.accept(cls);
}
}
private void collectImplementors(LoadedTypeDefinition type, Set toProcess) {
Set implementors = interfaceHierarchy.get(type);
if (implementors == null) return;
for (LoadedTypeDefinition child : implementors) {
toProcess.add(child);
if (child.isInterface()) {
collectImplementors(child, toProcess);
} else {
visitReachableSubclassesPreOrder(child, toProcess::add);
}
}
}
public void visitReachableSubclassesPreOrder(LoadedTypeDefinition type, Consumer function) {
Set subclasses = classHierarchy.get(type);
if (subclasses == null) return;
for (LoadedTypeDefinition sc : subclasses) {
function.accept(sc);
visitReachableSubclassesPreOrder(sc, function);
}
}
public void visitReachableSubclassesPostOrder(LoadedTypeDefinition type, Consumer function) {
Set subclasses = classHierarchy.get(type);
if (subclasses == null) return;
for (LoadedTypeDefinition sc : subclasses) {
visitReachableSubclassesPostOrder(sc, function);
function.accept(sc);
}
}
public void visitInitializedTypes(Consumer function) {
for (LoadedTypeDefinition t: initializedTypes) {
function.accept(t);
}
}
public void visitReachableTypes(Consumer function) {
for (LoadedTypeDefinition t: classHierarchy.keySet()) {
function.accept(t);
}
for (LoadedTypeDefinition t: interfaceHierarchy.keySet()) {
function.accept(t);
}
}
/*
* Package level methods, to allow a ReachabilityAnalysis to add methods/types to the info
*/
ReachabilityAnalysis getAnalysis() {
return analysis;
}
void addReachableInterface(LoadedTypeDefinition type) {
if (isReachableInterface(type)) return;
interfaceHierarchy.computeIfAbsent(type, t -> ConcurrentHashMap.newKeySet());
for (LoadedTypeDefinition i: type.getInterfaces()) {
addReachableInterface(i);
addInterfaceEdge(type, i);
}
// For every instance method that is not already invokable,
// check to see if it has the same selector as an "overridden" invokable method.
outer:
for (MethodElement im : type.getInstanceMethods()) {
if (!isInvokableMethod(im)) {
for (LoadedTypeDefinition si : type.getInterfaces()) {
MethodElement sm = si.resolveMethodElementInterface(im.getName(), im.getDescriptor());
if (sm != null && isInvokableMethod(sm)) {
LOGGER.debugf("\tnewly reachable interface: enqueued implementing method: %s", im);
analysis.processReachableInstanceMethodInvoke(im, null);
continue outer;
}
}
}
}
}
void addInterfaceEdge(LoadedTypeDefinition child, LoadedTypeDefinition parent) {
interfaceHierarchy.computeIfAbsent(parent, t -> ConcurrentHashMap.newKeySet()).add(child);
}
void addReachableClass(LoadedTypeDefinition type) {
if (isReachableClass(type)) return;
classHierarchy.computeIfAbsent(type, t -> ConcurrentHashMap.newKeySet());
LoadedTypeDefinition superClass = type.getSuperClass();
if (superClass != null) {
addReachableClass(superClass);
classHierarchy.get(superClass).add(type);
}
for (LoadedTypeDefinition i: type.getInterfaces()) {
addReachableInterface(i);
addInterfaceEdge(type, i);
}
// force class to be loaded (will fail if new reachable classes are discovered after ADD)
type.getVmClass();
}
void addInstantiatedClass(LoadedTypeDefinition type) {
instantiatedClasses.add(type);
}
void addInitializedType(LoadedTypeDefinition type) {
if (isInitializedType(type)) return;
if (type.isInterface()) {
addReachableInterface(type);
} else {
addReachableClass(type);
}
initializedTypes.add(type);
}
void addInvokableMethod(MethodElement meth) {
this.invokableMethods.add(meth);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy