Improvements in reflection:

1. During dependency analysis, propagate class literals from
   Class.forName return node
2. Use original class source to generate reflection metadata
3. Link classes when they appear in signature of reflectable methods
4. Turn Class.forName(string_literal) into class literal.
This commit is contained in:
Alexey Andreev 2018-01-27 00:21:50 +03:00
parent f49ec16021
commit ff7232ac3e
8 changed files with 90 additions and 9 deletions

View File

@ -26,15 +26,20 @@ import org.teavm.model.Instruction;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.StringConstantInstruction;
public class ClassForNameTransformer implements ClassHolderTransformer {
private static final MethodReference getNameMethod = new MethodReference(Class.class, "getName", String.class);
private static final MethodReference forNameMethod = new MethodReference(Class.class, "forName", String.class,
boolean.class, ClassLoader.class, Class.class);
private static final MethodReference forNameShortMethod = new MethodReference(Class.class, "forName",
String.class, Class.class);
private static final MethodReference initMethod = new MethodReference(Class.class, "initialize", void.class);
@Override
@ -42,17 +47,18 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
for (MethodHolder method : cls.getMethods()) {
Program program = method.getProgram();
if (program != null) {
transformProgram(program);
transformProgram(program, innerSource);
}
}
}
private void transformProgram(Program program) {
private void transformProgram(Program program, ClassReaderSource classSource) {
DisjointSet varSet = new DisjointSet();
for (int i = 0; i < program.variableCount(); i++) {
varSet.create();
}
int[] nameIndexes = new int[program.variableCount()];
String[] constants = new String[program.variableCount()];
Arrays.fill(nameIndexes, -1);
for (BasicBlock block : program.getBasicBlocks()) {
@ -64,6 +70,9 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
nameIndexes[invoke.getReceiver().getIndex()] = invoke.getInstance().getIndex();
}
}
} else if (instruction instanceof StringConstantInstruction) {
StringConstantInstruction stringConstant = (StringConstantInstruction) instruction;
constants[stringConstant.getReceiver().getIndex()] = stringConstant.getConstant();
} else if (instruction instanceof AssignInstruction) {
AssignInstruction assign = (AssignInstruction) instruction;
varSet.union(assign.getAssignee().getIndex(), assign.getReceiver().getIndex());
@ -74,6 +83,7 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
nameIndexes = Arrays.copyOf(nameIndexes, varSet.size());
int[] nameRepresentatives = new int[nameIndexes.length];
Arrays.fill(nameRepresentatives, -1);
String[] constantsByClasses = new String[varSet.size()];
for (int i = 0; i < program.variableCount(); i++) {
int varClass = varSet.find(i);
@ -83,6 +93,8 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
if (nameIndexes[i] >= 0) {
nameIndexes[varClass] = varSet.find(nameIndexes[i]);
}
constantsByClasses[varClass] = constants[i];
}
for (BasicBlock block : program.getBasicBlocks()) {
@ -92,18 +104,31 @@ public class ClassForNameTransformer implements ClassHolderTransformer {
}
InvokeInstruction invoke = (InvokeInstruction) instruction;
if (!invoke.getMethod().equals(forNameMethod)) {
if (!invoke.getMethod().equals(forNameMethod) && !invoke.getMethod().equals(forNameShortMethod)) {
continue;
}
Variable representative;
int classNameIndex = invoke.getArguments().get(0).getIndex();
int nameIndex = nameIndexes[classNameIndex];
if (nameIndex < 0) {
String constant = constantsByClasses[invoke.getArguments().get(0).getIndex()];
if (nameIndex >= 0) {
representative = program.variableAt(nameRepresentatives[nameIndex]);
} else if (constant != null) {
if (classSource.get(constant) == null) {
continue;
}
ClassConstantInstruction classConstant = new ClassConstantInstruction();
classConstant.setConstant(ValueType.object(constant));
classConstant.setReceiver(program.createVariable());
classConstant.setLocation(invoke.getLocation());
invoke.insertPrevious(classConstant);
representative = classConstant.getReceiver();
} else {
continue;
}
Variable representative = program.variableAt(nameRepresentatives[nameIndex]);
InvokeInstruction initInvoke = new InvokeInstruction();
initInvoke.setLocation(invoke.getLocation());
initInvoke.setType(InvocationType.SPECIAL);

View File

@ -54,6 +54,9 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
Constructor[].class);
private MethodReference getMethods = new MethodReference(Class.class, "getDeclaredMethods",
Method[].class);
private MethodReference forName = new MethodReference(Class.class, "forName", String.class, Boolean.class,
ClassLoader.class, Class.class);
private MethodReference forNameShort = new MethodReference(Class.class, "forName", String.class, Class.class);
private boolean fieldGetHandled;
private boolean fieldSetHandled;
private boolean newInstanceHandled;
@ -62,11 +65,17 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
private Map<String, Set<MethodDescriptor>> accessibleMethodCache = new LinkedHashMap<>();
private Set<String> classesWithReflectableFields = new LinkedHashSet<>();
private Set<String> classesWithReflectableMethods = new LinkedHashSet<>();
private DependencyNode allClasses;
public ReflectionDependencyListener(List<ReflectionSupplier> reflectionSuppliers) {
this.reflectionSuppliers = reflectionSuppliers;
}
@Override
public void started(DependencyAgent agent) {
allClasses = agent.createNode();
}
public Set<String> getClassesWithReflectableFields() {
return classesWithReflectableFields;
}
@ -83,6 +92,11 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
return accessibleMethodCache.get(className);
}
@Override
public void classReached(DependencyAgent agent, String className, CallLocation location) {
allClasses.propagate(agent.getType(className));
}
@Override
public void methodReached(DependencyAgent agent, MethodDependency method, CallLocation location) {
if (method.getReference().equals(fieldGet)) {
@ -97,14 +111,29 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
method.getVariable(0).getClassValueNode().addConsumer(type -> {
if (!type.getName().startsWith("[")) {
classesWithReflectableFields.add(type.getName());
ClassReader cls = agent.getClassSource().get(type.getName());
for (FieldReader field : cls.getFields()) {
linkType(agent, field.getType());
}
}
});
} else if (method.getReference().equals(getConstructors) || method.getReference().equals(getMethods)) {
method.getVariable(0).getClassValueNode().addConsumer(type -> {
if (!type.getName().startsWith("[")) {
classesWithReflectableMethods.add(type.getName());
ClassReader cls = agent.getClassSource().get(type.getName());
for (MethodReader reflectableMethod : cls.getMethods()) {
linkType(agent, reflectableMethod.getResultType());
for (ValueType param : reflectableMethod.getParameterTypes()) {
linkType(agent, param);
}
}
}
});
} else if (method.getReference().equals(forName) || method.getReference().equals(forNameShort)) {
allClasses.connect(method.getResult().getClassValueNode());
}
}
@ -213,6 +242,14 @@ public class ReflectionDependencyListener extends AbstractDependencyListener {
});
}
private void linkType(DependencyAgent agent, ValueType type) {
while (type instanceof ValueType.Array) {
type = ((ValueType.Array) type).getItemType();
}
if (type instanceof ValueType.Object) {
agent.linkClass(((ValueType.Object) type).getClassName(), null);
}
}
private void linkClassIfNecessary(DependencyAgent agent, MemberReader member, CallLocation location) {
if (member.hasModifier(ElementModifier.STATIC)) {

View File

@ -159,7 +159,7 @@ public class ClassGenerator implements Generator, Injector, DependencyPlugin {
ReflectionDependencyListener reflection = context.getService(ReflectionDependencyListener.class);
Set<MethodDescriptor> accessibleMethods = reflection.getAccessibleMethods(className);
ClassReader cls = context.getClassSource().get(className);
ClassReader cls = context.getInitialClassSource().get(className);
if (cls == null) {
return;
}

View File

@ -250,7 +250,8 @@ public class JavaScriptTarget implements TeaVMTarget, TeaVMJavaScriptHost {
if (debugEmitterToUse == null) {
debugEmitterToUse = new DummyDebugInformationEmitter();
}
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse, classes,
RenderingContext renderingContext = new RenderingContext(debugEmitterToUse,
controller.getUnprocessedClassSource(), classes,
controller.getClassLoader(), controller.getServices(), controller.getProperties(), naming);
renderingContext.setMinifying(minifying);
Renderer renderer = new Renderer(sourceWriter, asyncMethods, asyncFamilyMethods,

View File

@ -41,6 +41,7 @@ import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.debugging.information.DummyDebugInformationEmitter;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReference;
import org.teavm.model.ListableClassReaderSource;
@ -859,6 +860,11 @@ public class Renderer implements RenderingManager {
return classSource;
}
@Override
public ClassReaderSource getInitialClassSource() {
return context.getInitialClassSource();
}
@Override
public ClassLoader getClassLoader() {
return classLoader;

View File

@ -33,6 +33,7 @@ import org.teavm.debugging.information.DebugInformationEmitter;
import org.teavm.interop.PlatformMarker;
import org.teavm.model.AnnotationReader;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
@ -41,6 +42,7 @@ import org.teavm.model.ValueType;
public class RenderingContext {
private final DebugInformationEmitter debugEmitter;
private ClassReaderSource initialClassSource;
private ListableClassReaderSource classSource;
private ClassLoader classLoader;
private ServiceRepository services;
@ -53,10 +55,12 @@ public class RenderingContext {
private final Map<MethodReference, InjectorHolder> injectorMap = new HashMap<>();
private boolean minifying;
public RenderingContext(DebugInformationEmitter debugEmitter, ListableClassReaderSource classSource,
public RenderingContext(DebugInformationEmitter debugEmitter,
ClassReaderSource initialClassSource, ListableClassReaderSource classSource,
ClassLoader classLoader, ServiceRepository services, Properties properties,
NamingStrategy naming) {
this.debugEmitter = debugEmitter;
this.initialClassSource = initialClassSource;
this.classSource = classSource;
this.classLoader = classLoader;
this.services = services;
@ -64,6 +68,10 @@ public class RenderingContext {
this.naming = naming;
}
public ClassReaderSource getInitialClassSource() {
return initialClassSource;
}
public ListableClassReaderSource getClassSource() {
return classSource;
}

View File

@ -18,6 +18,7 @@ package org.teavm.backend.javascript.spi;
import java.util.Properties;
import org.teavm.common.ServiceRepository;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodReference;
import org.teavm.model.ValueType;
@ -25,6 +26,8 @@ import org.teavm.model.ValueType;
public interface GeneratorContext extends ServiceRepository {
String getParameterName(int index);
ClassReaderSource getInitialClassSource();
ListableClassReaderSource getClassSource();
ClassLoader getClassLoader();

View File

@ -41,6 +41,7 @@ public class ClassLookupDependencySupport extends AbstractDependencyListener {
if (cls == null) {
return;
}
MethodReader initMethod = cls.getMethod(new MethodDescriptor("<clinit>", void.class));
if (initMethod != null) {
agent.linkMethod(initMethod.getReference(), location).use();